a chat with no chosen directory runs in the caller's own home
The default was DATA_PATH/<email>/general_chat_sessions, a dedicated directory so /chat sessions formed their own Claude project group instead of cluttering the home. It is a sibling of the home, and confineUserTree makes every sibling the platform's at 0700 because the others are attachments and email_accounts. So it was unreachable for a member: the first live member turn started there and every Bash call failed on its own working directory before doing anything. A per-member copy inside each home fixed the symptom and left two rules to remember. The owner chose one rule instead — the account's own home, whoever they are — and accepted the trade knowingly: /chat sessions now share a project group with anything else run from that home, which was the reason the dedicated directory existed. Removed rather than left dangling: getGeneralChatSessionsCwd, ensureGeneralChatSessionsCwd, ensureMemberChatCwd, and general_chat_sessions from USER_DIRS so new accounts stop getting it. Existing directories are untouched and their transcripts stay where they are — Claude groups by cwd, so the owner's old /chat history remains under its own project slug rather than moving. The UI labels move with it: the default group now reads "home" rather than naming a directory that no longer has a role. ChatIdentity keeps carrying both email and home. The pairing was justified in the comment by general_chat_sessions being email-derived, which is now gone — but the distinction it encodes is real (the email says who, the home says where), so the comment explains that instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import * as errors from '@@/custom-errors';
|
||||
import * as sidecar from '@@/sidecar-registry';
|
||||
import { getUserSettings } from 'officerdb';
|
||||
import {
|
||||
getGeneralChatSessionsCwd,
|
||||
listClaudePwds,
|
||||
listClaudeSessions,
|
||||
loadClaudeSession,
|
||||
@@ -71,22 +70,22 @@ export const chatRouter = createRouter();
|
||||
// reviewer who had not written it. If you are reverting this, revert to a refusal — not to a narrower one.
|
||||
|
||||
// The working directory a request operates on: an explicit ?cwd= (a chosen pwd), else the default
|
||||
// general_chat_sessions dir. Claude groups sessions by cwd, so this selects which project group we read.
|
||||
// caller's own home. Claude groups sessions by cwd, so this selects which project group we read.
|
||||
// OpenCode runs on one fixed serve, but each session records the directory its turn ran in, so cwd
|
||||
// selects there too.
|
||||
const cwdOf = (ctx: Context, email: string): string => ctx.req.query('cwd')?.trim() || getGeneralChatSessionsCwd(email);
|
||||
const cwdOf = (ctx: Context, home: string): string => ctx.req.query('cwd')?.trim() || home;
|
||||
|
||||
// GET /chat/pwds — the default /chat dir plus every directory that already has Claude sessions.
|
||||
chatRouter.get('/pwds', async (ctx) => {
|
||||
const who = await chatIdentity(ctx.get('user'));
|
||||
return ctx.json({ pwds: listClaudePwds(who), default: getGeneralChatSessionsCwd(who.email) });
|
||||
return ctx.json({ pwds: listClaudePwds(who), default: who.home });
|
||||
});
|
||||
|
||||
// GET /chat/sessions[?cwd=] — conversations for a working directory, merged across both harnesses
|
||||
// (Claude transcripts + OpenCode's session store), newest first.
|
||||
chatRouter.get('/sessions', async (ctx) => {
|
||||
const who = await chatIdentity(ctx.get('user'));
|
||||
const cwd = cwdOf(ctx, who.email);
|
||||
const cwd = cwdOf(ctx, who.home);
|
||||
const claude = listClaudeSessions(who, cwd).map((s) => ({ ...s, harness: 'claude' as const }));
|
||||
const opencode = await listOpenCodeSessions(cwd);
|
||||
const sessions = [...claude, ...opencode].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
||||
@@ -102,7 +101,7 @@ chatRouter.get('/sessions', async (ctx) => {
|
||||
chatRouter.get('/sessions/:id', async (ctx) => {
|
||||
const who = await chatIdentity(ctx.get('user'));
|
||||
const id = ctx.req.param('id');
|
||||
const cwd = cwdOf(ctx, who.email);
|
||||
const cwd = cwdOf(ctx, who.home);
|
||||
// Fall back to a by-id scan when the (default) cwd doesn't hold it — a fresh /chat/<id> deep-link/refresh
|
||||
// doesn't know the session's cwd. The returned detail carries the real cwd for the client to scope the UI.
|
||||
const detail = isOpenCodeSessionId(id)
|
||||
@@ -212,7 +211,7 @@ chatRouter.get('/live', async (ctx) => {
|
||||
chatRouter.delete('/sessions/:id', async (ctx) => {
|
||||
const who = await chatIdentity(ctx.get('user'));
|
||||
const id = ctx.req.param('id');
|
||||
const cwd = cwdOf(ctx, who.email);
|
||||
const cwd = cwdOf(ctx, who.home);
|
||||
const ok = isOpenCodeSessionId(id) ? await deleteOpenCodeSession(id) : deleteClaudeSession(who, cwd, id);
|
||||
if (!ok) return ctx.text('Not found', 404);
|
||||
return ctx.json({ ok: true });
|
||||
@@ -222,7 +221,7 @@ chatRouter.delete('/sessions/:id', async (ctx) => {
|
||||
chatRouter.patch('/sessions/:id/title', async (ctx) => {
|
||||
const who = await chatIdentity(ctx.get('user'));
|
||||
const id = ctx.req.param('id');
|
||||
const cwd = cwdOf(ctx, who.email);
|
||||
const cwd = cwdOf(ctx, who.home);
|
||||
const { title } = await ctx.req.json<{ title?: string }>();
|
||||
if (!title?.trim()) return ctx.text('title is required', 400);
|
||||
const ok = isOpenCodeSessionId(id)
|
||||
|
||||
Reference in New Issue
Block a user