Repository navigation
Upgrading — read this before you sync
This release is mostly about not losing turns, and getting there needs a schema change.
Opening your archive with v0.3.0 runs 8 migrations (005 and 007–013) automatically. One of them matters operationally:
Migration 012 rebuilds the turns table. It holds the old and new copies at once, so it needs free disk space roughly equal to your turns table, plus room for the write-ahead log. On a large archive that is gigabytes, and the database file stays that much larger afterward until you run ccvault vacuum.
It is wrapped in a transaction and ends with a strict FTS integrity check, so a failure — including running out of disk — rolls the whole thing back rather than leaving a half-rebuilt table. But migrations do not take a backup for you (only sync --full and --rebuild do). So before upgrading, with nothing else running:
cp ~/.ccvault/ccvault.db ~/.ccvault/ccvault.db.bakTwo further cautions:
- Don't run an older ccvault against an upgraded archive. Older binaries open a newer schema without complaint, write with the wrong connection pragmas, and their
sync --fullwipes the archive instead of re-parsing it. Upgrade every copy on yourPATH. - Timing on a million-row archive is extrapolated, not measured — expect minutes, not seconds, and don't interrupt it.
What this fixes
- Turns lost to uuid collisions between resumed transcripts. A resumed session copies the earlier session's lines verbatim, uuids included, and
turnswas keyed on the uuid alone — so the later parse deleted the earlier session's rows. Migration 012 re-keys on(session_id, id)and un-marks the affected transcripts so the next sync recovers them. Measured on one real set: 23,905 turns stored before, 25,003 after. sync --fulldestroying the archive. It no longer resets the database; re-parsing is now a separate--rebuild, it backs up first, confirms interactively, and refuses to run unattended.- Full-text index orphans, which made searches miss turns that were present.
- Failed tool calls are now searchable —
is_erroris stored, so you can find the calls that didn't work. statsgained--json, search reaches tool inputs and results, and date-filtered searches no longer scan the whole index.
Known issue: a transcript that repeats a uuid within itself still stores fewer turns than it reports (#121). That case is narrower than the cross-session one fixed here, and tracked separately.
Changelog
Features
- ccc4c67: feat(mcp): list_sessions/list_projects paginate by offset like search (@detour1999)
- ec09bf2: feat: --data-dir / --config make the whole CLI runnable against a fixture (@detour1999)
- 586b0b7: feat: --full sync backs up, confirms, and refuses non-interactive wipes (@detour1999)
- cbeb94c: feat: add GetToolNamesLike for tool-name suggestions (@harperreed)
- 1eb0be2: feat: add empty-result hints and has_more to MCP search and list tools (@harperreed)
- a45dc45: feat: add nanoclaw source adapter with subagent sidechain support (@detour1999)
- 6c098a9: feat: archive subagent transcripts from ~/.claude as their own sessions (@detour1999)
- 2885b2b: feat: bound a date-filtered search's index scan to the window it asks for (@detour1999)
- 6ef9fab: feat: bounded line reader for parser (defense against pathological input) (@detour1999)
- 257776f: feat: cap line reads at 200 MB and truncate raw_json above 1 MB (@detour1999)
- 5e50e82: feat: ccvault stats gains --json flag for machine-parseable output (@detour1999)
- a2572ab: feat: ccvault sync reports sessions whose transcript was rewritten upstream (@detour1999)
- b6ebb4a: feat: count and surface JSONL lines skipped during parse (@detour1999)
- 9d28aee: feat: get_turns resumes from an ordinal, and the summary reports the cursor (@detour1999)
- 684ccb7: feat: index a day and month token beside the searchable text (@detour1999)
- 533380c: feat: internal/compact width-aware compaction + TUI layout tiers (follow-up 8) (@detour1999)
- 7d84ebe: feat: introduce projectref discipline + AST allowlist (follow-ups 4-8) (@detour1999)
- 36faa55: feat: migration 005 normalizes display_name to basename for existing rows (@detour1999)
- 24c7a1c: feat: migration 009 stores and indexes tool payloads, backfilled from raw_json (@detour1999)
- cbbe01c: feat: ordering reads off ordinal, and get_turns reports it as a cursor (@detour1999)
- b355b02: feat: raw_json placeholder for oversized parsed turns (@detour1999)
- be00190: feat: reclaim dead space with verified copy-and-swap compaction (@detour1999)
- 6480a01: feat: search reaches tool inputs and results, and says when it did (@detour1999)
- 0f480a3: feat: sessions.parent_session_id, with a backfill for ids that carried the link (@detour1999)
- 1b47068: feat: stats reports turns whose stored payload is not JSON (@detour1999)
- b32ffe9: feat: store the tool-result is_error flag, so failed calls are findable (@detour1999)
- 4868423: feat: subagent sessions are hidden from listings, never unreachable (@detour1999)
- cae3d5a: feat: surface raw_json truncation counts in sync stats (@detour1999)
- 202c1c0: feat: the parser and all five adapters carry tool payloads instead of dropping them (@detour1999)
- 167824f: feat: tool-payload storage policy as a shared package (@detour1999)
- e8ef709: feat: turns carry a gapless per-session ordinal, backfilled from rowid (@detour1999)
Bug fixes
- 2f964a4: fix(search): parse dates in local time and tolerate bad timestamps (@MrBeldum)
- 04d8aad: fix: --full re-parses the archive instead of destroying it (@detour1999)
- a05efae: fix: --full sync invalidates analytics parquet cache (@detour1999)
- 601b7f9: fix: CLI errors print once, not twice (#69) (@detour1999)
- 6dba7df: fix: GetTurns drops invalid raw_json so downstream json.Marshal doesn't crash (@detour1999)
- 41ac166: fix: MCP answers every request, and says which turns it could not read (@detour1999)
- 633f8d1: fix: MCP initialize advertises the real build version, not a literal 0.1.0 (@detour1999)
- 8176186: fix: ResetAll runs inside a transaction so mid-reset failure rolls back (@detour1999)
- 5eefac0: fix: SQLite DSN options were never applied; put the archive in real WAL (@detour1999)
- 119988f: fix: TUI startup stall, swallowed search keys, and the dead '?' binding (@detour1999)
- b173ec1: fix: UpsertSession follows a session file that moved on disk (@detour1999)
- c075ee3: fix: a NULL in any nullable session column no longer fails the scan (@detour1999)
- 7666542: fix: a NULL token column no longer zeroes a project's whole total (@detour1999)
- 3e5c9f8: fix: a focused text input owns printable runes; footers match the key map (@detour1999)
- 12b2852: fix: a payload hit resolves to the turn in its own session (@detour1999)
- be25ed4: fix: a replaced turn no longer leaves a ghost entry in turns_fts (@detour1999)
- aaf9edf: fix: a turn's identity is its session and its uuid, not the uuid alone (@detour1999)
- cc5b0f8: fix: address 5 blocking findings from adversarial review (@detour1999)
- c741307: fix: address code review feedback on #12 (@detour1999)
- c2cc18f: fix: an upsert carrying no source_file keeps the path already on the row (@detour1999)
- fc35304: fix: cancel the TUI sync goroutine on navigate-away and ctrl+c (@detour1999)
- 020b65f: fix: collect orient stat-query errors into warnings (@harperreed)
- 7478302: fix: get_session folds the large-session notice into warnings[] (@detour1999)
- b147a17: fix: give tool_uses a key, and clean up what its absence left behind (@detour1999)
- 9053c9c: fix: guard against duplicate migration versions, match tool fragments literally (@detour1999)
- 0f70cbe: fix: make tool: search filter case-insensitive and correct tool-name docs (@harperreed)
- 68027f8: fix: nanoclaw adapter propagates non-notfound errors from ScanClaudeHome (@detour1999)
- ba37739: fix: notifications must not receive JSON-RPC responses (#7) (@detour1999)
- 35839c6: fix: pin the release and lint toolchains, migrate brews to homebrew_casks (@detour1999)
- a6a58bf: fix: real-tmux --full test exposed 2 UX/safety gaps, patch both (@detour1999)
- fa1b1a5: fix: round-2 adversarial review — 28 findings across code + tests + docs (@detour1999)
- 68fc1d3: fix: round-3 adversarial review — 14 findings, mostly regressions from round 2 (@detour1999)
- 2c32e29: fix: satisfy CI's older gosec G602 (slice OOB) in TestTurnCRUD (@detour1999)
- c56bf61: fix: scan git-tracked files, not the filesystem, for DisplayName reads (@detour1999)
- 38333fd: fix: scope the result backfill to a session, and stop jeff guessing at an unmatched id (@detour1999)
- 9c6709b: fix: search LEFT JOINs projects so a projectless session is findable (@detour1999)
- 6edd201: fix: search_conversations reports degraded enrichment instead of hiding it (@detour1999)
- e4b2cd5: fix: session read paths tolerate a NULL project_id instead of erroring (@detour1999)
- 6417733: fix: session reads return has_error and has_subagent instead of always false (@detour1999)
- bdd969e: fix: session time bounds are the extrema of its turns, not the ends of the file (@detour1999)
- 12d2f9d: fix: size the SESSION ID column to its ids, and show SUBS in the TUI list (@detour1999)
- 3e055ed: fix: strip ANSI before matching goreleaser's deprecation output (@detour1999)
- 7488852: fix: surface MCP enrichment failures as warnings instead of dropping them (@harperreed)
- 3b9e733: fix: the commit stamp admits a dirty tree, like the version already did (@detour1999)
- 1fcece5: fix: the sync tick is the sync's heartbeat, not the focused view's (@detour1999)
- da43bb0: fix: three defects the fresh-eyes pass found (@detour1999)
- ea306de: fix: turns_fts correctness no longer rests on a connection pragma (@detour1999)
- e0ffbca: fix: use bufio.Reader in parser to survive oversized JSONL lines (@detour1999)
- b7a3dff: fix: verify the WAL is fully folded before deleting it, and keep WAL mode (@detour1999)
- 5cd1c3d: fix: version reports the commit and date releases already inject (@detour1999)
Other
- 98e8254: address coderabbit feedback (@beaugunderson)
- 90cfb57: build: surface gofmt's doc-comment quote rewrite, unblock parallel lint (#66) (@detour1999)
- 32546af: ci: make the Go version matrix mean what it says (#70) (@detour1999)
- f32bc4e: ci: pin the release toolchain so a tag cannot build with an unchosen Go (@detour1999)
- 5c260e6: perf: let the payload attribution start from the turn, not from the index (@detour1999)
- 6f02e92: refactor(mcp): get_analytics reports all warnings at one top-level key (@detour1999)
- cfba37c: refactor(mcp): one warning shape, one warning per lookup, plus test gaps (@detour1999)
- 83a3248: refactor: MCP response-field reads fail with a diagnostic instead of panicking (@detour1999)
- 55c7e23: refactor: delete UpdateProjectStats, dead since ReconcileProjectAggregates (@detour1999)
- 59e9765: refactor: inject io.Writer into MCP Server for testable output (@detour1999)
- 8b8232f: refactor: return ParseStats from parser instead of bare int (@detour1999)
- e780dd7: revert(search): keep before: exclusive and after: strict (@detour1999)
- 454424b: revert: keep
brews:, ship only the version pins (@detour1999) - 6fdab03: update project name storage, fix several bugs (@beaugunderson)