forgetting a conversation needs both of its ids, not one twice
`chat_session_events` is keyed on officer's SESSION KEY. The other three tables are keyed on the harness's TRANSCRIPT ID. Both are uuids for the same conversation, and `forgetChatSessions` took one list and passed it to all four — so the events delete matched nothing. Not sometimes: by construction, every time. A deleted conversation left its entire durable output behind. It survived two verifications because counting events BY TRANSCRIPT ID also returns zero by construction, and zero read as "cleaned". What settled it was reading an orphaned event instead of counting it — the payload carries both ids side by side and says outright which key the row is filed under. So the parameter is now named for its id space. That is the whole defence and it is the only one available: nothing else distinguishes the two, since both are uuids of the same shape, and a positional `string[]` cannot be got wrong loudly. Empty `sessionKeys` is legitimate and common — a session spawned by `claude -p` outside officer's websocket never had a key and never wrote an event, which is exactly a commander's children. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { Context } from 'hono';
|
||||
import { emitToSession } from './websocket';
|
||||
import { emitToSession, sessionKeyForTranscript } from './websocket';
|
||||
import { pauseBackgroundTask, restartBackgroundTask } from './task-restart';
|
||||
import { createRouter } from '../../create-router';
|
||||
import * as errors from '@@/custom-errors';
|
||||
@@ -422,11 +422,17 @@ chatRouter.delete('/sessions/:id', async (ctx) => {
|
||||
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);
|
||||
/*
|
||||
The transcript is gone; forget what the platform stored ABOUT it. Four tables describe a conversation with
|
||||
no foreign key — there is no row for one 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 is gone.
|
||||
|
||||
Two id spaces, and they are not interchangeable: three tables are keyed on the transcript id, while
|
||||
`chat_session_events` is keyed on officer's session key. Undefined here is the normal case for an old
|
||||
conversation — no live session holds it, so nothing is keyed on a key to remove.
|
||||
*/
|
||||
const liveKey = sessionKeyForTranscript(id);
|
||||
await forgetChatSessions({ transcriptIds: removed, sessionKeys: liveKey ? [liveKey] : [] });
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user