Skip to content

feat(agent-session): sweep orphaned external session files - #17143

Merged
0xfullex merged 8 commits into
mainfrom
link-session-cleanup-external-store
Jul 20, 2026
Merged

feat(agent-session): sweep orphaned external session files#17143
0xfullex merged 8 commits into
mainfrom
link-session-cleanup-external-store

Conversation

@DeJeune

@DeJeune DeJeune commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

🚨 Branch strategy — read before opening this PR

The v2 refactor has merged into main, so main is the default branch for active development (v1 and v2 code currently coexist there — expect large, breaking changes).

  • Active development (features, refactors, optimizations, fixes for the current codebase) → target main (the default base).
  • v1 maintenance (hotfixes and subsequent v1 releases) → branch from and target v1, not main.

A v1 fix does not auto-carry to main: if the same bug exists on main, open a separate forward-port PR targeting main. Before touching subsystems being replaced, read docs/references/data/ and watch for @deprecated markers — they flag code being deleted.

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>.jsonl and <id>/subagents/…), per-session stores (session-env/, file-history/, tasks/, todos/) under Cherry's CLAUDE_CONFIG_DIR, and the auto-created system workspace dirs under Data/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, AgentSessionRuntimeService builds an AgentSessionLiveIndex (session ids / resume tokens still known to the DB or the in-memory runtime), removes dead system-workspace dirs itself, and invokes the new optional sweepSessionFiles(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/services gains only one pure read (hasRuntimeResumeToken), and deletion/edit semantics stay plain DB writes.

The following tradeoffs were made:

  • Cleanup is eventual (≤ 24 h age guard + 30 min interval), not immediate on delete; the guard protects SDK session ids the DB cannot know yet (in-flight first turns, warm queries).
  • The user's real ~/.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:

  • Delete-time events from the data services (pre-delete token collection + post-commit emitters) — rejected because it weaves file-cleanup side effects through AgentSessionService/AgentSessionMessageService delete paths.
  • Sweeping ~/.claude for 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

  • Runtime-agnostic contract: drivers without an external session store simply omit sweepSessionFiles (src/main/ai/runtime/types.ts).
  • Sweep safety rules live in src/main/ai/runtime/claudeCode/sessionFileSweep.ts: uuid-only judgment (never touches memory/, 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.

  • Branch: This PR targets the correct branch — main for active development, v1 for v1 maintenance fixes
  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior.
  • Self-review: I have reviewed my own code (e.g., via /gh-pr-review, gh pr diff, or GitHub UI) before requesting review from others

Release note

Disk space used by deleted agent sessions (transcripts and per-session runtime caches) is now reclaimed automatically by a periodic background cleanup.

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>
@DeJeune
DeJeune requested a review from 0xfullex as a code owner July 17, 2026 04:27
@DeJeune
DeJeune requested a review from vaayne July 17, 2026 04:41

@vaayne vaayne left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/main/ai/agentSession/AgentSessionRuntimeService.ts Outdated
DeJeune and others added 2 commits July 19, 2026 17:56
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>
@DeJeune
DeJeune requested a review from vaayne July 20, 2026 00:35

@vaayne vaayne left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@DeJeune
DeJeune requested a review from EurFelux July 20, 2026 03:15

@EurFelux EurFelux left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/main/ai/runtime/claudeCode/sessionFileSweep.ts
@EurFelux

Copy link
Copy Markdown
Collaborator

Follow-up architecture discussion: #17183

This PR remains a practical stopgap for reclaiming existing Cherry-managed orphaned artifacts. The follow-up issue discusses making AgentSession a lifecycle-managed main-process business entity, moving runtime-aware mutations behind an owning service/IPC boundary, and reducing periodic filesystem GC to legacy reconciliation rather than the primary resource-lifetime mechanism.

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>
@DeJeune
DeJeune requested a review from EurFelux July 20, 2026 04:36

@EurFelux EurFelux left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warm-query lifecycle race is addressed, the cleanup ordering is now explicit, and CI passes. #17183 tracks the broader AgentSession lifecycle architecture separately. LGTM.

@DeJeune DeJeune added the ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval. label Jul 20, 2026
@0xfullex
0xfullex merged commit 71d9939 into main Jul 20, 2026
6 checks passed
@0xfullex
0xfullex deleted the link-session-cleanup-external-store branch July 20, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge This PR has sufficient reviewer approvals but is awaiting code owner approval.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants