deleting a conversation forgets what the platform stored about it
Found by a sanity check after two commanders were deleted through the UI: the Command HQ tables cascaded perfectly and four OTHER tables kept a row each, for ever. 24 orphaned rows in chat_session_models, 26 in chat_session_events, against 14 live transcripts. Nothing cascades because there is nothing to cascade FROM. All four are keyed on a transcript id, and a transcript is a file the harness writes — there is no row for a conversation, so the database cannot help and the delete path has to be explicit. It was not. Three of the four are inert: a model, a bell preference, a durable copy of turn output nothing will ask for again. `nudges` is not — it is a SCHEDULE, read by the tick in nudger.ts. A nudge belonging to a deleted conversation is a timer that keeps firing at something that no longer exists. That is what turned this from untidy into worth doing now. deleteClaudeSession returns the IDS it removed rather than a boolean, because a `/clear` chain is several transcripts presenting as one conversation and forgetting the head while orphaning its parts is the same bug at a smaller scale. THE TRAP THAT CHANGE INTRODUCED, caught before shipping: every call site said `if (deleteClaudeSession(...))` and an empty array is TRUTHY in JavaScript, so a delete that found nothing would have reported success. The typecheck was clean — `string[]` is a perfectly good truthy expression. All three sites test `.length` now. Fifth silently-wrong thing today that a typecheck could not see. Cleared the existing orphans with the same function the delete path now uses, after checking the one thing that would have made it wrong: whether any of them belonged to a member, whose transcripts live in a home this scan does not read. There is one account on this machine, so they were all the owner's. Two chat_session_events rows appeared again immediately after, and are not a leak: the durable log is written independently of the transcript — that is what makes turn output survive officer being down — so a session that has just begun legitimately has events before a transcript exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
getChatNotifyPreference,
|
||||
getChatNotifyPreferences,
|
||||
setChatNotifyPreference,
|
||||
forgetChatSessions,
|
||||
} from 'officerdb';
|
||||
import type { NotifyPreference } from 'officerdb/types';
|
||||
import { setNudge, getNudge, cancelNudge } from 'officerdb';
|
||||
@@ -376,8 +377,21 @@ chatRouter.delete('/sessions/:id', async (ctx) => {
|
||||
const id = ctx.req.param('id');
|
||||
const cwd = cwdOf(ctx, who.home);
|
||||
if (isOpenCodeSessionId(id) && !who.isOwner) return ctx.text('Not found', 404);
|
||||
const ok = isOpenCodeSessionId(id) ? await deleteOpenCodeSession(id) : deleteClaudeSession(who, cwd, id);
|
||||
if (!ok) return ctx.text('Not found', 404);
|
||||
if (isOpenCodeSessionId(id)) {
|
||||
if (!(await deleteOpenCodeSession(id))) return ctx.text('Not found', 404);
|
||||
return ctx.json({ ok: true });
|
||||
}
|
||||
|
||||
// `.length`, not truthiness: this returns the IDS it removed and an empty array is truthy in JavaScript,
|
||||
// so `if (removed)` would report a delete that found nothing as a success.
|
||||
const removed = deleteClaudeSession(who, cwd, id);
|
||||
if (!removed.length) return ctx.text('Not found', 404);
|
||||
|
||||
// The transcript is gone; forget what the platform stored ABOUT it. Four tables are keyed on a transcript
|
||||
// id with no foreign key — there is no row for a conversation to reference — so nothing cascades and this
|
||||
// has to be explicit. One of them, `nudges`, is a live schedule: left behind it keeps firing at a
|
||||
// conversation that no longer exists.
|
||||
await forgetChatSessions(removed);
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user