feat(agent-session): sweep orphaned external session files - #17143
Conversation
Session deletion and message edits were DB-only: transcripts and per-session caches the agent runtime persisted outside the DB (Claude Code's CLAUDE_CONFIG_DIR projects/session-env/file-history/tasks/todos, plus auto-created system workspace dirs) were orphaned forever. Reconcile them with a runtime-agnostic mark-and-sweep GC instead of delete-time hooks, keeping the data layer side-effect-free: - AgentSessionRuntimeService sweeps on boot and every 30 min: it builds an AgentSessionLiveIndex (DB rows via existing reads plus in-memory runtime state) and removes dead system workspace dirs itself. - Drivers with an external session store implement the new optional sweepSessionFiles(live) hook; runtimes without one omit it. - The Claude Code sweeper GCs only Cherry's own config dir, judging system-workspace project dirs by session id and shared stores by resume token, with a 24 h mtime guard protecting in-flight state. The user's real ~/.claude is deliberately left alone. - Data layer adds only a pure read (hasRuntimeResumeToken). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: suyao <sy20010504@gmail.com>
vaayne
left a comment
There was a problem hiding this comment.
The filesystem deletion boundaries look conservative, but the periodic sweep currently performs repeated unindexed synchronous SQLite probes on the Electron main thread. Please materialize persisted token liveness once per sweep before merging.
The session-file sweep probed the unindexed `runtime_resume_token` column once per on-disk token across projects/session-env/file-history/tasks/todos. Since better-sqlite3 runs synchronously on the Electron main thread, a large history could turn each periodic sweep into many full-table scans and stall the app. Snapshot the referenced resume tokens into a Set once at the start of the sweep and make `isResumeTokenLive` an in-memory lookup. The 24h min-age guard still protects tokens minted after the snapshot; at worst a concurrent deletion is reclaimed on the next sweep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: suyao <sy20010504@gmail.com>
EurFelux
left a comment
There was a problem hiding this comment.
The periodic sweep is effective as a stopgap for Cherry-managed artifacts, but the whole-directory cleanup needs to account for still-live warm queries before it is safe to merge.
|
Follow-up architecture discussion: #17183 This PR remains a practical stopgap for reclaiming existing Cherry-managed orphaned artifacts. The follow-up issue discusses making |
Review [A7] (#17143): a deleted session's prewarmed query can outlive its DB row within the warm TTL after its idle entry left the runtime index — the whole-directory removals (project dir, system workspace cwd) could then race a live subprocess, and the 24h token age guard never protected them. Close warm queries for dead sessions at the start of the Claude sweep (warm entries are keyed by session id), and reorder the host sweep so driver sweeps — whose contract now includes releasing still-held session resources — run before the workspace directories are removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: suyao <sy20010504@gmail.com>
What this PR does
Before this PR:
Deleting an agent session (or editing its messages away) only removed DB rows. Everything the runtime persisted outside the DB was orphaned forever: Claude Code transcripts (
projects/<encoded-cwd>/<id>.jsonland<id>/subagents/…), per-session stores (session-env/,file-history/,tasks/,todos/) under Cherry'sCLAUDE_CONFIG_DIR, and the auto-created system workspace dirs underData/Agents/<sessionId>.After this PR:
A runtime-agnostic mark-and-sweep GC reconciles external session stores against the DB — on boot and every 30 minutes,
AgentSessionRuntimeServicebuilds anAgentSessionLiveIndex(session ids / resume tokens still known to the DB or the in-memory runtime), removes dead system-workspace dirs itself, and invokes the new optionalsweepSessionFiles(live)driver hook. The Claude Code sweeper GCs only Cherry's own config dir (system-workspace project dirs judged by session id, shared stores by resume token, 24 h mtime guard protecting in-flight turns and prewarmed queries). Historical orphans from past deletions are collected too.Fixes: N/A
Why we need it and why it was done in this way
Orphaned transcripts grow multi-MB per session and are never reclaimed. A sweep was chosen over delete-time hooks so the data layer stays side-effect-free:
src/main/data/servicesgains only one pure read (hasRuntimeResumeToken), and deletion/edit semantics stay plain DB writes.The following tradeoffs were made:
~/.claude(external-CLI/login providers) is deliberately not swept: it also holds their terminal CLI sessions and possibly other Cherry installs' data; Claude Code's own retention cleanup (cleanupPeriodDays) owns that dir.The following alternatives were considered:
AgentSessionService/AgentSessionMessageServicedelete paths.~/.claudefor exact Cherry-owned tokens — rejected as unsafe (shared namespace with the user's own CLI and other installs).Links to places where the discussion took place: N/A
Breaking changes
None. Cleanup is background-only; on first launch after upgrade, previously orphaned session files under Cherry's own data dir are removed (they belong to already-deleted sessions).
Special notes for your reviewer
sweepSessionFiles(src/main/ai/runtime/types.ts).src/main/ai/runtime/claudeCode/sessionFileSweep.ts: uuid-only judgment (never touchesmemory/,sessions-index.json), per-token judgment in shared project dirs, whole-dir judgment only for Cherry-exclusive system-workspace cwds.Checklist
This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR.
Approvers are expected to review this list.
mainfor active development,v1for v1 maintenance fixes/gh-pr-review,gh pr diff, or GitHub UI) before requesting review from othersRelease note