Repository navigation
acpmux: a chat keeps its Claude transcript past Claude's retention cleanup - #24802
austinywang wants to merge 23 commits into
Conversation
…tion cleanup Red: keep and restore do nothing yet, and a first turn cut short is still refused even when Claude stored it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eanup Claude Code deletes transcripts past cleanupPeriodDays (here about a day). A chat resumed after that found nothing to --resume and started a fresh conversation with only its plain text: the agent lost every tool call and what it had read. Each chat now holds a hard link to its transcript in its own session folder, kept at each turn's end, and links it back (with a fresh time) before a start resumes it. A first turn cut short (an app update mid-turn) now resumes what Claude stored instead of starting over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… chats Review fixes: - Pooled agents and agents adopted from their host after a restart note their Claude store too, so their turns keep the transcript. - A transcript still in the store is made new before a resume, so the resuming process's own cleanup cannot take it. - A first turn cut short resumes only when Claude stored a prompt. - Keeps run one at a time and read a recreated file to its end before the rename; lines the kept file already holds are not appended again. - A fork (and a fork through a turn) puts back the transcript the chat it forks kept. - A chat deleted meanwhile is not given its folder back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After putting back the transcript a fork starts from, only the chat's own conversation is kept in its folder: the parent's goes with the parent. A keep that finds the kept file already holds Claude's lines points Claude's name at it again instead of leaving later lines unkept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard was still in scope at the await, so every future that pools a chat stopped being Send. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
web and size checks: green |
Correctness review (subagent, two rounds) and resolutionRound 1 on da660e3: nothing blocking; six should-fix findings, all fixed in d380872:
Round 2 on d380872: all six confirmed fixed, no deadlock (the two lock takes are sequential, never across an await). New should-fix: a fork kept a link to its parent's transcript in its own folder. Fixed in 90ec2b1 (keep after restore only for the chat's own conversation); the relink nit in the already-merged case is fixed there too. Hosted run on d380872 found a std Left as accepted nits: a few-syscall window between the last read and the rename in the merge; the sibling scan costs one Own verdict on bbb952c: diff read in full, the pool change only scopes the guard; lint red is base-only ( |
This comment has been minimized.
This comment has been minimized.
|
Bead |
Held: root cause corrected, and a regression foundThe root cause in this description is wrong. Claude Code's own retention here is its 30-day default: an isolated sweep deleted only a 40-day-old probe and kept the 20-, 8-, 3-, 1.5- and 0.5-day ones. The deleter is OptChat Chief. Its turn and subagent project settings carry Reproduced: a Claude started in a folder whose project settings say 2 deleted the 3-, 8-, 20- and 40-day probes of another project, transcripts and companion folders alike. The root-cause fix is a separate Chief PR. Regressions this PR would add, as it stands:
What was verified with the real Claude CLI: a hard link survives Claude's appends (same inode across turns). With Claude's copy deleted, Converted to draft. With the Chief fix in, this PR only helps chats idle for more than 30 days, so it needs narrowing (no preset or Chief sessions, plus the companion folder) before it is worth its cost. |
…anicking Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…still writing Review findings on #24802: keep the old file open across the merge's rename so a line appended after the last read still joins the kept transcript; leave a recreated file alone while its first line is half written; never make a deleted chat's folder again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, on any volume After the merge's rename, append what the old file still held through Claude's name, so a copy on another volume gets it too; the kept file when Claude's name was deleted again meanwhile. Review follow-up on #24802. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review (claude-subagent): SHIP at 9a76842 Independent correctness review (read-only subagent), two passes: d8ef448 then 9a76842. Fixed in this PR
Advisory, not fixed
Also merged the latest |
…ext-chat-resume-full-history
…ext-chat-resume-full-history
|
For awareness: this conflicts with Failing checks at e105ab9: |
|
Review (codex gpt-6-astra high, fast): BLOCK at e105ab9 This is for awareness; nothing was pushed here. The PR also still conflicts with The reviewer's findings at this head. I spot-checked 1 and 2 in
Checks at this head, for when it is updated: the failing jobs are clippy (changed cmux-tui packages), seven-language live conformance, the musl/darwin |
Resolves hub/pool/mod.rs: base's set_agent_session_id(.., "claude") keeps the id history, and this branch's note_claude_store still runs after the lock is released. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Sweep fix. Blocker: conflict in |
|
Independent review for awareness (codex gpt-6-astra, high, fast; read-only, no builds), at head
Static review only; no modifications, builds, or tests. VERDICT: BLOCK at 599195a — Merge retries can lose transcript records, and pooled/adopted sessions can miss retention entirely. |
|
For awareness: this PR conflicts with feat-cmux-next, so it can't land until it's rebased or base is merged in. Its current review verdict stays as is; a hand-resolved merge gets a fresh review at the new head. |
Summary
Chats resumed after a while came back without their history: the agent started a fresh conversation with only the chat's plain text (at most 24,000 characters, no tool calls or results) and forgot what it had read and done. Claude Code deletes transcripts past its retention (
cleanupPeriodDays), so--resumefound nothing. The ~1-day loss on the reporting machine came from OptChat Chief: its turn and subagent settings carrycleanupPeriodDays: 2, and Claude's cleanup sweeps every project of the store it runs on (see the 2026-10-24 hold comment; that root cause is a separate Chief fix). Claude's own default is 30 days, and this PR keeps a chat's conversation past either. acpmux also refused to resume any chat whose first turn never finished (an app update mid-turn), even when Claude had written that turn to disk.Each chat now keeps a hard link to its Claude transcript in its own session folder (
~/.acpmux/sessions/<chat>/claude/<project>/<id>.jsonl), taken at each turn's end. Before a start resumes or forks a conversation, a transcript Claude deleted is linked back into Claude's store and its time is set to now, so the resuming process's own cleanup can't take it. A Claude that recreated the file after a deletion has its new lines merged onto the kept transcript. A link costs no copy and sees every line Claude appends; across volumes it falls back to a copy. Claude's companion folder (<id>/: tool results, subagent transcripts) is kept the same way, file by file, and every resume puts back whatever of it Claude's cleanup took (Claude's own newer files win; a file Claude wrote again never cuts its kept copy short). Deleting the chat deletes its folder and the transcript with it.Chief sessions (turns, compactors, subagents:
cmux.chieftag,optchat-name oroptchat-preset) keep nothing, so Chief's own short retention still applies to them. Those sessions are never deleted, and keeping their transcripts would keep them for good (the regression the hold found).The first-turn refusal now resumes when Claude stored a prompt for the conversation, and still starts fresh when it did not.
Covered entry points: a cold start (resume and
--fork-session), a pooled agent, an agent adopted from its host after a restart, and a fork through an earlier turn (the parent's kept copy is found in its folder).Testing
Eight regression tests in
cmux-tui/crates/acpmux/src/hub/claude_keep.rs, run with./scripts/verify-cmux-tui-hosted.sh --filter claude_keep:Base with stubs (9b91d6f): 4 of 5 failed (
a_transcript_claude_deleted_is_put_back_whole,a_transcript_put_back_is_not_past_the_retention,lines_written_after_a_deletion_join_the_kept_transcript,a_first_turn_cut_short_resumes_what_claude_stored); the fifth asserts nothing is restored, which a stub also satisfies. https://github.com/manaflow-ai/cmux/actions/runs/40064180785Head (bbb952c): Rust tests pass (Linux, filtered to
claude_keep, 8 tests). https://github.com/manaflow-ai/cmux/actions/runs/40066863762Head (9a76842), after merging the latest feat-cmux-next and the review fixes: Rust tests pass (Linux, filtered to
claude_keep, 10 tests, including the newa_first_line_still_being_written_is_left_alone). https://github.com/manaflow-ai/cmux/actions/runs/40249428388The hosted lint job is red on files this PR does not touch (
hub/dispatch.rs,server/requests.rs,cmux-link), on both the red and the fix commits../scripts/verify-cmux-tui-hosted.sh --filter claude_keep, 14 tests including the newthe_companion_folder_is_put_back_with_the_transcript,the_companion_folder_comes_back_when_claude_wrote_its_transcript_again,a_companion_file_claude_wrote_again_does_not_cut_the_kept_oneanda_chief_session_keeps_no_transcript. Each companion test fails on the commit before its fix (traced by the reviewer; the companion files are missing or the kept one is cut short).Observed / Not run
Observed: the reporting chat's acpmux log shows
resume_failed: Claude conversation … never finished a turn, so a fresh one startsat 04:35:09Z, after~/.claude/.last-cleanupat 04:34:38Z; neither that conversation's transcript nor another chat's (resumedexactthree times the day before) exists anywhere under the home folder. The chat pane's history paging was replayed against the daemon and loads every turn, so the pane is not involved.Not run: a tagged build dogfood of a resume after a real retention cleanup, and the macOS test job (it stops at the base lint failure). Transcripts Claude already deleted before this change cannot be recovered.
Review: an independent correctness subagent reviewed two rounds; its findings (pooled and adopted agents, stale times, prompt-less files, keep races, forks of old chats, deleted chats, forks keeping the parent's transcript) are fixed in d380872 and 90ec2b1. The hosted run also caught a lock guard held across an await in the pooled path, fixed in bbb952c.
Changelog
Fixed: A chat resumed after a day or more keeps its whole conversation instead of starting over with only its text
Proof
No capture: daemon-side change with no UI.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Fixes chats losing their Claude transcript after Claude's retention cleanup, so a resume after a day or more keeps the full conversation instead of starting fresh with only the chat's plain text.
Claude Code deletes transcripts past
cleanupPeriodDays(about a day), leaving--resumewith nothing to load. Each chat now keeps a hard link to its transcript in its own session folder, refreshed at each turn's end. Before a start resumes or forks, a deleted transcript is linked back into Claude's store with a fresh timestamp so the resuming process's own cleanup can't remove it. If Claude recreated the file after a deletion, its new lines are merged onto the kept transcript without loss or duplication; a half-written first line is left alone. A first turn cut short by an app update now resumes when Claude stored the prompt, instead of starting over.Written for commit 599195a. Summary will update on new commits.