Skip to content

feat: auto-merge a run's session into one file at exit - #78

Open
lroolle wants to merge 1 commit into
mainfrom
feat/auto-merge-on-exit
Open

feat: auto-merge a run's session into one file at exit#78
lroolle wants to merge 1 commit into
mainfrom
feat/auto-merge-on-exit

Conversation

@lroolle

@lroolle lroolle commented Jul 30, 2026

Copy link
Copy Markdown
Member

A resumed session fragments across one trace-*.jsonl per run; the viewer stitches them at read time, but the disk never converges unless someone remembers cctrace merge --yes. Now every capture run consolidates its OWN sessions on the way out.

What it does

  • At exit (after flush, before the receipt names any file), the run collects the session ids it saw on the wire and runs a session-scoped merge: union into session-.jsonl, prune fully-subsumed sources.
  • Fires only when there is something to consolidate: >1 source file for the sid, or an existing session-* output. A fresh single-file session's trace stays byte-identical.
  • When the run's own trace is absorbed, the "Reopen anytime" line and the registry tombstone point at the session file instead of a path that no longer exists.
  • --no-auto-merge opts out of just this; --fresh opts out of the whole continuity layer. Legacy node mode untouched. Works for every client (claude/codex/grok/kimi) via extractSessionId.

Correctness

  • planMerge(logDir, { sessionIds, fragmentedOnly }): scoped prune-ability is stricter than the whole-dir sweep -- a source is prunable only when every pair lands in a session THIS plan writes. A file also holding an unmerged session's pairs (e.g. a concurrent run) is never pruned. Regression-tested.
  • Whole-dir cctrace merge behavior is unchanged (with all sessions written the new predicate reduces to the old one); existing merge tests pass untouched.
  • Keeps applyMerge's discipline: atomic writes, union-never-shrink, re-stat before each unlink (a live capture appending mid-merge is skipped, not truncated).
  • Fail-soft: wrapped so a merge error logs one dim line and never costs the exit receipt or exit code.

Verification

  • bun test: 578 pass, 0 fail (7 new scoped-merge tests + flag parse test).
  • End-to-end with a fake client through the base-url proxy: run 1 untouched -> run 2 merged 2 sources and pruned both -> run 3 grew the existing session file. Both opt-outs suppress it; manual merge --prune --yes output unchanged.

🤖 Generated with Claude Code

A resumed session used to leave one trace-*.jsonl per run; every capture
run now merges its OWN sessions into session-<sid8>.jsonl at exit and
prunes the sources it fully absorbed. Scoped by the sids seen on the
wire (planMerge's new MergeScope), so a concurrent run's trace is never
touched, and gated on there being something to consolidate — a fresh
single-file session's trace stays byte-identical.

The scoped plan needs a stricter prune rule than the whole-dir sweep: a
source is prunable only when every pair lands in a session THIS plan
writes, or a file also holding an unmerged session's pairs would vanish
into no output. --no-auto-merge opts out; --fresh already does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant