Persist Claude and Codex freshness digests across engine restarts - #1393
Conversation
Claude and Codex Fingerprint content-hashes the whole transcript, and the in-memory verified-source gate only spares that cost from a process's second pass onward. A daemon restart or a one-shot CLI sync therefore re-reads and hashes every unchanged transcript — about 28GB across ~54k files on one real archive — before a single skip fires. Implement MultiFileStatHasher for both providers so the engine's provider_freshness stat-digest short-circuit applies: a digest over (size, mtime, ctime) tuples is stamped after verified outcomes and persists in SQLite, letting a fresh engine skip unchanged sources with two stats and one small DB read. The ctime term preserves in-place rewrite detection, matching the verified-source gate's signature. The Codex digest folds session_index.jsonl exactly like the gate's sidecar signature, so an index-only title rename always breaks the digest, and providerStatFreshnessMtime returns the index-folded effective mtime so cold and warm cache keys stay on the same basis.
roborev: Combined Review (
|
The digest-currency check used path-only lookups, so a TraeX skip could borrow a newer Codex row on the same rollout path and hide a project repair. Scope every lookup to the provider's own stored-agent labels (codebuff+freebuff for the relabeling Codebuff provider, the discovery label elsewhere) and update the hasher-registration test for the agents that now declare the capability. Stamp the provider_freshness digest on every skip that verifies current content against a stored current row: the Claude content-verified single-session skip, the hash-validated cache skip, and the Codex-family DB-fingerprint skip. Rows that predate the side-table now backfill on their first confirmed-unchanged pass instead of re-hashing on every fresh process, and a shared session_index.jsonl touch no longer leaves every unaffected rollout with a permanently stale digest. The digest gate also runs before the Claude single-session content guard so a digest match skips without reading the transcript.
roborev: Combined Review (
|
…t-digest-skip * origin/main: ci: isolate slow Windows Go packages (#1395) fix(activity): avoid repeated snapshot peer scans (#1394) Keep in-chunk Codex tool outputs on the incremental parse path (#1391) Stop re-parsing titled Codex sessions when session_index.jsonl is absent (#1389) fix(parser): match Codex fork turns to parents (#1384)
Merge main and repair the newCodexSessionBuilder test call the #1384 + #1391 merge left non-compiling (also fixed on main by #1397). Withhold stat-digest staging, stamping, and consultation for Claude and Codex-family sources under a pathRewriter: a remote import materializes a fresh physical file whose mtime is copied from the remote and whose ctime is the import clock, so a same-stat different-content re-download can collide with a stored digest inside one coarse filesystem timestamp tick and skip a real rewrite (caught by TestSyncAllCodexPathRewriterSameStatRewriteUsesContentHash on Linux runners). Remote freshness for these providers stays content-hash arbitrated; Codebuff keeps its deliberate remote digest under the logical key.
roborev: Combined Review (
|
Persisted digest hits returned before they could establish in-memory source trust. Each warm pass repeated digest stats and database repair checks, which pushed allocations over the benchmark gate. Promote verified local state after a trusted digest match and consult that state before computing another digest. Fresh engines retain the persisted shortcut, while later passes return to the established warm path.
roborev: Combined Review (
|
A transient session index read or scan failure looked like a confirmed absence. The engine could persist a new stat digest while retaining a stale title, so later restarts stopped retrying the title lookup. Keep transcript skips independent, but promote or persist freshness trust only after the title index is readable or confirmed absent. This preserves the warm path while ensuring transient metadata failures get another chance.
roborev: Combined Review (
|
Claude transcripts can emit a main session and fork sessions from one file. Stamping freshness after the first successful row could hide a failed fork forever after a restart. Keep existing members stale before writes. Promote every member and persist the digest only after the complete source succeeds, so partial writes remain retryable.
roborev: Combined Review (
|
Treat excluded and trashed Claude DAG members as resolved without promoting or counting them as writes, so active branches can complete without repeated rewrites. Persist complete-source incremental digests after the database commit, withhold them for partial EOF records, and invalidate source freshness when a trashed member is restored.
roborev: Combined Review (
|
A restored fork could remain stale when the unchanged transcript's main row satisfied Claude's early freshness shortcut. That shortcut could then restore the digest without parsing the fork. Require every active row for the source path to have the current data version before accepting stem-based freshness.
roborev: Combined Review (
|
A per-result retry or CWD veto was treated as retry state for every member emitted by the source. Valid siblings stayed below the current data version and were rewritten on later syncs.\n\nKeep source-wide retry state for completion and freshness decisions, while applying provider-wide and per-session retry state to each write.
roborev: Combined Review (
|
PostgreSQL kept restored sessions at the current data version. A session could therefore appear fresh even when its source changed while it was in trash. Match SQLite's restore contract by invalidating the restored row's data version. The next source sync can then refresh its content.
roborev: Combined Review (
|
A stale data version on a user-trashed session defeated persisted source digests after every restart. Unchanged transcripts were hashed again even though trashed rows do not require parser repair. Check repair state only across active rows. An all-trashed source remains resolved until restore invalidates its digest and marks its row stale.
roborev: Combined Review (
|
Restored S3 sessions rely on their stale data version to override an otherwise matching in-memory skip entry. This contract lacked direct end-to-end coverage, so cache ordering could appear to preserve content changed while in trash. Exercise restoration with unchanged object metadata and fingerprint. Confirm that sync fetches the source, stores its changed message, and promotes the row to the current data version.
roborev: Combined Review (
|
Claude and Codex reduced an unavailable change-time to zero inside an otherwise valid size and mtime tuple. A same-stat rewrite could therefore match a persisted digest and skip content hashing after restart. Treat the whole digest as unverified and reject its zero sentinel before the freshness lookup. The stat shortcut now applies only when every existing component supplies a reliable change-time.
roborev: Combined Review (
|
…1413) Warm startup reconciliation could consume several CPU cores for tens of minutes on large archives. The agent-scoped freshness query shape introduced in #1349 allowed SQLite to choose the broad agent index, so each discovered source could scan every stored session for that provider. These lookups now require the existing source-path index, keeping the reconciliation path and the digest-backed freshness path from #1393 bounded by the matching source rows without adding an index migration. Long startup is now identifiable and controllable before runtime publication. Startup state includes the daemon build version and operating-system process create time; status displays that version, while stop and restart can safely target the exact starting process. Managed Caddy identity is published at launch so forced startup shutdown also cleans up the proxy. Legacy startup snapshots without process identity remain protected from signaling. This is independent of #1374, which changes provider controls and watcher scheduling but does not change these database lookups or startup-state lifecycle behavior. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
…dy-core * origin/main: (25 commits) ci: retry failed Docker image builds (kenn-io#1437) Speed up HTTP sync by processing only changed sessions (kenn-io#1414) chore: remove dead code and unused frontend exports (kenn-io#1434) fix(parser): populate Kimi session cwd (kenn-io#1427) feat(frontend): add raw and formatted tool output display (kenn-io#1424) feat(insights): support OpenAI-compatible endpoints (kenn-io#1430) fix(parser): support legacy Zed thread schemas (kenn-io#1429) perf(signals): index duplicate prompt comparisons (kenn-io#1425) fix(config): honor explicit empty agent directory arrays (kenn-io#1423) feat(parser): add DeepSeek Harness session support (kenn-io#1402) test(sync): wait for archive audit stall state (kenn-io#1422) chore(deps): update github actions dependencies (kenn-io#1421) Tombstone stored Claude sessions a complete full parse no longer emits (kenn-io#1392) fix(sync): report stalled syncs as unhealthy (kenn-io#1419) Reduce idle CPU and let users turn off unused providers (kenn-io#1374) fix(serve): survive dual-stack port collisions at startup (kenn-io#1406) fix(sync): bound startup reconciliation and expose daemon identity (kenn-io#1413) fix(deps): update module golang.org/x/mod to v0.40.0 [security] (kenn-io#1400) Persist Claude and Codex freshness digests across engine restarts (kenn-io#1393) fix(sync): add lifecycle logging (kenn-io#1399) ...
Summary
Persist file-stat digests for Claude and Codex so syncs after an engine restart
can skip unchanged transcripts without reopening and hashing them.
session_index.jsonl, so title-only changes still resync.and existing rows are backfilled on their first verified pass.
are not a reliable freshness signal.
On a 55k-session archive, a fresh-process sweep fell from more than 30 minutes
to about 39 seconds. Warm-process sweeps remain behaviorally unchanged.
The main implementation is in
internal/parser/stat_digest.goandinternal/sync/engine.go, with restart and index-change coverage in the parserand sync integration tests.