Skip to content

fix(status): serve menubar-json status from a disk-persisted snapshot to eliminate per-poll re-parse latency - #999

Open
dgabehar wants to merge 2 commits into
getagentseal:mainfrom
dgabehar:feature/tom-b05c182a-codeburn-perf-fix
Open

fix(status): serve menubar-json status from a disk-persisted snapshot to eliminate per-poll re-parse latency#999
dgabehar wants to merge 2 commits into
getagentseal:mainfrom
dgabehar:feature/tom-b05c182a-codeburn-perf-fix

Conversation

@dgabehar

Copy link
Copy Markdown

Summary

codeburn status --format menubar-json took 25-90+ seconds per call
regardless of cache state, causing the polling CodeBurnMenubar.app to
stall/stop refreshing. Root cause (confirmed via live profiling): the
menubar spawns a fresh CLI process per poll, so every call re-parses the
full session-cache blob and re-runs the full aggregation pipeline with
zero cross-process reuse — none of the existing in-process caches
(parser.ts's TTL/burst maps, session-cache.ts's cacheMemo) ever survive
between polls.

This adds a disk-persisted status snapshot keyed by a cheap corpus
fingerprint (stat-only pass over discovered sources — dev/ino/mtime/size,
no session-cache.json parse, no transcript content read), with a
settle-window debounce (CODEBURN_STATUS_SNAPSHOT_SETTLE_MS, default
2000ms, capped 60000ms) so rapid-fire source writes coalesce into one
recompute instead of one per poll tick.

Related

#971 covers the same general problem class (expensive redundant
per-invocation recompute) from a different angle — a resident
codeburn serve daemon sometimes being bypassed on cold start. This PR
is orthogonal to that: the menubar's status --format menubar-json path
has no resident-process story at all today (a fresh CLI process every
poll, always), so the fix here is a disk-persisted snapshot rather than
daemon-sharing. Happy to reconcile approaches if useful once #972 lands.

Changes

  • src/session-cache.ts — disk-persisted status snapshot
    (loadStatusSnapshot/saveStatusSnapshot) keyed by corpus fingerprint
    • resolved query; atomic temp+rename write at 0600; schema-version
      guard against a payload-shape drift across binary upgrades; orphaned
      temp-file sweep extended to cover the new file.
  • src/parser.tscomputeCorpusFingerprint, a stat-only pass over
    every discovered source (expanding Claude's directory-shaped sources
    to their actual .jsonl files first, since a directory's own mtime
    doesn't move when an existing file inside it is rewritten in place).
  • src/main.ts — wires the status --format menubar-json path to check
    the snapshot before parsing/aggregating, and to persist a fresh one
    after a real recompute; a single shared options object now feeds both
    the cache key and the buildMenubarPayloadForRange call so they can't
    drift apart.
  • tests/cli-status-menubar.test.ts — new coverage: snapshot-hit
    byte-identical output, debounce-defer during the settle window,
    reflect-after-settle.
  • SPEC-perf-cache-fix.md — design write-up (root-cause investigation,
    why the debounce lives in the snapshot layer rather than
    reconcileFile, and why a first attempt at the latter was reverted).

Test plan

  • npm test — full suite passes, including the new
    tests/cli-status-menubar.test.ts coverage.
  • npx tsc --noEmit clean.
  • Verified live against a real ~386MB session-cache corpus: cold call
    ~18-23s (unchanged — that's the underlying parse+aggregate cost, not
    addressed by this fix), settled repeat call ~2s, byte-identical
    payload.
  • Manually installed via npm link and run against the real
    CodeBurnMenubar.app.

Checklist

  • Code follows project conventions and coding standards
  • Unit tests written and passing
  • No new secrets, credentials, or API keys committed
  • Release notes updated (docs/release-notes.md + README.md)
  • npm test green

Doug Gabehart added 2 commits August 14, 2026 14:38
… to eliminate per-poll re-parse latency

codeburn status --format menubar-json took 25-90+ seconds per call because
the menubar app spawns a fresh CLI process per poll, so every call
re-JSON.parse'd the full session-cache blob and re-ran the full
aggregation pipeline with no cross-process reuse. Adds a disk-persisted
status snapshot keyed by a cheap corpus fingerprint (stat-only, no
content read), with a settle-window debounce so rapid-fire source
writes coalesce into one recompute instead of one per poll.
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