Split out of #689, which is now fixed for the part that mattered (the data loss). This is the remaining half.
Behaviour
In a Claude home Paddock does not own, DELETE /api/projects/:slug/chats/:sessionId releases the session rather than removing the transcript (#692) — correct, since the file is the user's own terminal claude history and there is no Paddock copy to delete.
But the chat stays in the chat list. unadoptSession only drops an adoption record, and a chat Paddock created is rediscovered structurally: the engine scans <claudeHome>/projects/<encoded-cwd>/, finds the JSONL, and lists it again.
Net effect: the transcript is safe and the user's stated intent — "take this out of my list" — is not honoured. The response says { ok: true, removed: false, retained: true }, so a caller can tell, but nothing acts on it.
Asserted as current behaviour in test/integration/delete-chat-unowned-home.test.ts ("does NOT yet remove it from the list (the tombstone is still owed)") so it cannot drift silently.
Fix
A tombstone: a sidecar recording "Paddock must not show this session", checked when listing. The four existing per-chat sidecars (ArchiveStore, StarStore, UnreadStore, ParentDetachStore) are the pattern to follow — same write-through JSON shape, keyed <agent>\0<sessionId>.
The natural choke point is HerdctlService.listSessions / recentSessions rather than the route handlers: every consumer (project chat list, Home aggregation, the sweeper's activity check) goes through those, so filtering there cannot miss a caller.
Why it was deferred
#692 keys this branch off ownsClaudeHome, and #691 removes that flag — Paddock will always own its home, with transcripts: own|host becoming the discriminator. A tombstone written against ownsClaudeHome would have to be rewritten one step later.
Best done alongside #691's transcripts lever, where host is the mode that makes it reachable at all. Low urgency: no data is at risk, and after #691 the state is only reachable by explicitly opting into transcripts: host.
Split out of #689, which is now fixed for the part that mattered (the data loss). This is the remaining half.
Behaviour
In a Claude home Paddock does not own,
DELETE /api/projects/:slug/chats/:sessionIdreleases the session rather than removing the transcript (#692) — correct, since the file is the user's own terminalclaudehistory and there is no Paddock copy to delete.But the chat stays in the chat list.
unadoptSessiononly drops an adoption record, and a chat Paddock created is rediscovered structurally: the engine scans<claudeHome>/projects/<encoded-cwd>/, finds the JSONL, and lists it again.Net effect: the transcript is safe and the user's stated intent — "take this out of my list" — is not honoured. The response says
{ ok: true, removed: false, retained: true }, so a caller can tell, but nothing acts on it.Asserted as current behaviour in
test/integration/delete-chat-unowned-home.test.ts("does NOT yet remove it from the list (the tombstone is still owed)") so it cannot drift silently.Fix
A tombstone: a sidecar recording "Paddock must not show this session", checked when listing. The four existing per-chat sidecars (
ArchiveStore,StarStore,UnreadStore,ParentDetachStore) are the pattern to follow — same write-through JSON shape, keyed<agent>\0<sessionId>.The natural choke point is
HerdctlService.listSessions/recentSessionsrather than the route handlers: every consumer (project chat list, Home aggregation, the sweeper's activity check) goes through those, so filtering there cannot miss a caller.Why it was deferred
#692 keys this branch off
ownsClaudeHome, and #691 removes that flag — Paddock will always own its home, withtranscripts: own|hostbecoming the discriminator. A tombstone written againstownsClaudeHomewould have to be rewritten one step later.Best done alongside #691's
transcriptslever, wherehostis the mode that makes it reachable at all. Low urgency: no data is at risk, and after #691 the state is only reachable by explicitly opting intotranscripts: host.