Skip to content

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

Open
dgabehar wants to merge 399 commits into
mainfrom
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#1
dgabehar wants to merge 399 commits into
mainfrom
feature/tom-b05c182a-codeburn-perf-fix

Conversation

@dgabehar

Copy link
Copy Markdown
Owner

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, not the original findings doc's hypothesis): 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. This fix adds a disk-persisted status snapshot keyed by a cheap corpus fingerprint, with a settle-window debounce so rapid-fire source writes coalesce into one recompute instead of one per poll.

Changes

  • src/session-cache.ts — disk-persisted status snapshot keyed by a cheap corpus fingerprint (stat-only, no content read)
  • src/parser.ts — settle-window debounce so rapid-fire source writes coalesce into one recompute instead of one per poll
  • src/main.ts — wires the status --format menubar-json path to read from the persisted snapshot instead of re-parsing + re-aggregating on every poll
  • tests/cli-status-menubar.test.ts — new coverage for the snapshot/fingerprint/debounce behavior
  • SPEC-perf-cache-fix.md — fix design spec

Rally Story

N/A

Test Plan

  • npm test (includes new tests/cli-status-menubar.test.ts coverage for the snapshot/fingerprint/debounce path)
  • Code-reviewed: PASS, 0 Critical/High
  • Security-reviewed: PASS, 0 Critical/High (one Medium finding on file permissions already remediated on this branch)

References

  • Original defect report: PERF-DEFECT-FINDINGS.md (repo root)
  • Produced via a BMAD-orchestrated Quick Dev cycle with independent code-review and security-review gates, both PASS

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)
  • context-packs validate passes (if directory files changed)
  • npm test green

Generated by Vera (GitHub Backplane) — Session: b05c*** | Dispatch: 9*

iamtoruk and others added 30 commits August 18, 2026 12:48
…e-republish

cache: stop republishing unchanged month shards on ranged runs
Hold Ink stdout columns/rows frozen during a SIGWINCH burst and emit
one settled resize, then rerender. Do not intercept writes, so a
mid-burst state update still paints even when net size is unchanged.

Fixes getagentseal#977.
Stop writing OAuth caches as Application Support JSON. Persist
CodeBurn-owned items in Keychain, secure-read and migrate leftover
0644 files only after read-back verification, and keep Claude from
storing a refresh token.
GLM-5.3 is missing from LiteLLM, so Hermes/Cline sessions priced at $0.
Alias it to the glm-5p2 sibling and keep the display name GLM-5.3.

The root Hermes profile was shown as project "default". Use "hermes"
for that profile only; named profiles and cwd inference are unchanged.

Scan Hermes transcripts for https GitHub pull URLs and attach them so
the Pull requests page can attribute those sessions.
Limit session_meta model extraction to direct payload fields so nested provenance metadata cannot overwrite turn_context. Invalidate cached parses and cover model switches with a regression test.
Hermes stores Cline Pass as cp/cline-pass/glm-5.3. One prefix strip
leaves cline-pass/glm-5.3, which missed the bare glm-5.3 alias and
stayed $0. Price the last path segment through the same aliases.
Tool dumps (gh pr list, changelog grep) were smearing one session
across dozens of pull URLs. Capture links from user and assistant
text only.
Price any OmniRoute/Cline/cmd/antigravity wrapper by peeling prefixes
and, for GLM 5.x, falling back to the newest priced sibling. Do not
require a new alias per model id.

Classify Hermes sessions by surface and workspace: ACP is the Buzz
app; project comes from git root or a real cwd, never $HOME or a
profile name.
Extra High review held getagentseal#1039. Stop pricing every future glm-5.x as 5.2,
stop collapsing unknown provider/org/model trees onto a priced leaf, and
stop calling ACP Buzz — source=acp is a transport and the DB has no
client field. Strip trailing punctuation on PR URLs and bump the Hermes
parse version so old cache rows reparse.
A valid Keychain item plus a leftover JSON used to skip chmod, so a
failed unlink could leave 0644 secrets on disk. Failed Disconnect also
cleared bootstrap and hid the retry. Repair leftover files to 0600,
keep bootstrap when Keychain delete fails, revalidate the opened fd,
and loop the secure read.
SuperGrok Heavy is a $300/mo preset compared to parsed API-equivalent
spend on a calendar month. CodexBar's live weekly window is a different
class. TUI and desktop copy now say budget / calendar month and deny
a live provider window. No Grok Connect is invented.
Extra High held getagentseal#1039 again. An unknown provider/model still became a
bare-model price via getCanonicalName's first-segment strip. Relative
cwd values like '.' could inherit the invoking repo. PR scrape treated
fenced dumps and other repos as attribution.

Peel only known vendor/router namespaces. Require an absolute
platform path before a Hermes cwd is a workspace. Ignore fenced URLs
and, when a git root exists, keep only that repo's pull links.
Bump the Hermes parse version so old cache rows reparse.
Extra High held 91754c5. z-ai/glm-5.2 went unpriced (Cline's real
vendor spelling). Forward-slash UNC became a workspace on POSIX.
PR matching used basename, so evil/codeburn collided. Tilde fences
were still scanned.

Add z-ai to known namespaces. Reject // UNC on POSIX. Attribute
PRs only when origin yields owner/repo. Strip ``` and ~~~ fences.
Bump Hermes parse version to v4.
Hermes and token-plan sessions store mimo-v2.5-pro. The snapshot
row is xiaomi/mimo-v2.5-pro. Same class as the existing
mimo-v2-flash alias. No invented rate.

Looking up the display name on the stripped leaf before following a
pricing alias, so cline-pass/mimo-v2.5-pro cannot recurse
strip → alias → last-segment forever.
Extra High MERGE AFTER FIX on 252ea92. Pathname chmod was
unverified. Disconnect hid retry when only the legacy file
survived. Secure read stopped at exactly maxBytes.

Tighten leftovers via opened-fd fchmod+fstat. Keep bootstrap
unless both Keychain and legacy deletes succeed. Read
maxBytes+1 so growth past the limit is rejected.
Extra High MERGE AFTER FIX on d3f86f5. mimo-v2.5 aliased to
xiaomi/mimo-v2.5 then last-segment recursed forever. Looking up
SHORT_NAMES before resolveAlias also froze user remaps of known
ids (gpt-4o still displayed as GPT-4o).

Follow user aliases first. Break strip→alias→leaf cycles.
Do not invent a Kimi rate. Do not paper over this with a
mimo-v2.5 SHORT_NAMES row.
CI typecheck failed: sessions-report maps getShortModelName, and
the Extra High cycle Set was a second parameter. Array.map fed
the index as `seen`.

Cycle tracking stays on an internal helper. Display and alias
behavior unchanged. No second Extra High.
…d month-shard cache re-layout

Upstream restructured the session cache from a flat session-cache.v7.json to
a month-sharded-by-provider layout (v8, now v9 via envelope re-layout) and
split parseAllSessions into a sync wrapper plus an AsyncLocalStorage-scoped
parseAllSessionsInCacheScope for correctness around concurrent cache-dir
changes.

- src/session-cache.ts: cleanupOrphanedTempFiles conflict resolved by keeping
  both sweeps upstream added (parent-dir versioned-cache-file sweep, and
  shard-dir blanket .tmp + unreferenced-shard sweep) but extending the
  parent-dir sweep to also catch status-snapshot.json.*.tmp, since that file
  lives in the parent cache dir (orthogonal to the month-shard subdirectory)
  and upstream's shard-dir sweep never sees it. Also repointed the two
  remaining getCacheDir() call sites in the status-snapshot section to
  getCodeburnCacheDir(), the renamed/relocated version of that helper.
- src/parser.ts: kept CorpusFingerprint/computeCorpusFingerprint verbatim
  (untouched by upstream's diff), took upstream's parseAllSessions /
  parseAllSessionsInCacheScope split entirely.

Verified: tsc --noEmit clean; tests/cli-status-menubar.test.ts (15/15),
tests/session-cache*.test.ts + tests/session-cache-shards.test.ts (119/119),
tests/parser*.test.ts (34/34) all pass. A full `vitest run` also surfaced 3
pre-existing failures confined to app/renderer/*.test.ts (missing jsdom in
that nested workspace's own node_modules) — unrelated to session-cache.ts/
parser.ts and outside this merge's scope.
…h-verification

ci: verify the 0.9.20 upgrade path on every platform
The `mimo-v2-flash -> xiaomi/mimo-v2-flash` alias shipped before this
branch and already cycled through display-name resolution, so
getShortModelName threw RangeError on every real MiMo v2 Flash session.
The new cycle-safe resolver fixes it, but nothing pinned the ids that
actually crashed in production: cover the four spellings found in a real
session cache, including the unnamespaced `mimo/mimo-v2-flash`.

Add the base `mimo-v2.5` display name so the row reads next to
"MiMo v2.5 Pro" instead of showing a raw slug; SORTED_SHORT_NAMES is
longest-first, so the Pro tier still wins its own entry.
The codex parse version and CODEX_CACHE_VERSION bumps in getagentseal#1040 make codex
sessions re-parse, but the daily cache has no per-provider invalidation, so
every day already finalized keeps its old per-model rows - and usage-aggregator
serves every day before today from that cache, with ten-year retention. Raise
DAILY_CACHE_VERSION and MIN_SUPPORTED_VERSION to 20 so history re-derives once
off the warm session cache.

The re-derivation test now seeds v19, the last shipped version, so it models the
real 19 -> 20 path, and the upgrade-path check expects daily-cache.v20.json.

Measured on a real 110-day cache: no day lost value, none disappeared, 100 came
back identical, and 9 grok days rose by $19.80 in total from rollups an earlier
parse change had left stale. Every codex model row was unchanged - that corpus
predates the provenance field the fix corrects.
iamtoruk and others added 30 commits August 24, 2026 07:15
A turn anchored before the range start survives range slicing whole, so
the aggregator can emit a residue day the parse never covered (getagentseal#1130,
found in getagentseal#1127's root-cause work). The merge guards already defuse the
overwrite; this stops the residue from reaching durable history at all.
Applied at both ranged call sites (re-derive and gap); the aggregator
and slicer semantics are untouched — whole-corpus callers still
aggregate undated parses freely.
…ission

The getagentseal#1126 test pinned basename presence; under getagentseal#1128's fail-closed
provenance the same fixture (no trusted provider-recorded cwd) correctly
omits the field entirely — stronger privacy, same zero-paths guarantee.
The positive basename-on-the-wire case is covered by
sync-project-provenance.
…-filter

fix(daily-cache): ranged parses never write days outside their range
…ect-privacy

fix(sync): trust only proven outbound project metadata
…an tab

Ports the desktop app's Gemini Code Assist quota flow (loadCodeAssist
tier + retrieveUserQuota buckets, ~/.gemini/oauth_creds.json read-only,
in-memory-only refresh) to the menubar. Plan tab and empty-state gating
now treat Gemini as plan-capable.
…ogo rows

Replaces the tab strip with a NavigationSplitView shell: search field,
General/About tile rows, and a data-driven Providers section whose real
logo marks render gray at rest and accent-tinted on hover, with green
connected dots and an N-on counter. Bundles template icon assets.
…an tab

Ports the desktop app's Copilot flow (editor-plugin OAuth token from
~/.config/github-copilot, copilot_internal/user snapshot) read-only,
with github.com-host preference and UI-string sanitization.
…icon, resource-bundle packaging

Settings window now replicates CodexBar's System Settings layout
(880x620 fullSizeContentView window, transparent titlebar, fixed 260pt
sidebar, SF Symbol gradient chips, native search field, grouped About
pane with Updates/Links). Status item renders the 0101 flame template
with severity tints. package-app.sh ships the SwiftPM resource bundle,
fixing the Bundle.module launch crash. Sidebar layout modeled on
CodexBar (MIT, steipete/CodexBar).
# Conflicts:
#	src/main.ts
#	src/parser.ts
…Settings pane, Plan tab

Ports the desktop app's Antigravity flow: ps+lsof discovery of the
local language server, Connect-RPC quota/status probes over loopback
HTTPS with the self-signed-cert trust override scoped strictly to
127.0.0.1, disconnected-state UX when the app is not running. Also
swaps all provider sidebar marks to the official LobeHub icon set.
Popover header now renders the full CodeBurn wordmark filled with the
site's .flame-text gradient (#ff6a00 > #ffda44 > #e8553a > #ff8c00,
300% width, 3s ease sweep). Static gradient under Reduce Motion.
Status item returns to the SF flame (the 0101 mark blurs at menubar
size); About hero shows the full-color binary flame and the About
sidebar row its bare white template, no chip; footer credits Resham
Joshi (iamtoruk) / AgentSeal, MIT; About tagline matches the website
(Your AI Bill, Itemized); UI copy drops em-dashes (currency rows use
middots); Settings window is resizable and fullscreen-capable and
recenters when restored off-screen.
The 0101-detail flame collapses into a faint smudge at 20px; the row
now uses a solid silhouette cut from the website flame's own outline.
…upe, em-dash sweep

Settings window titles itself after the visible pane like System
Settings; the popover subtitle adopts the website tagline (Your AI
Bill, Itemized); About stops printing dev (dev) when version and build
match; remaining user-facing em-dashes across popover and service
strings become sentence breaks.
…uota-parity

Menubar: Gemini/Copilot/Antigravity live quota, CodexBar-style Settings, brand refresh
…pshot

Second-round review fixes for the persisted menubar status snapshot
(getagentseal#999, ozymandiashh review 2026-08-21):

- queryKey now folds in getPricingGenerationKey() (src/models.ts): the
  live LiteLLM cache's freshness, the bundled snapshot's own content
  digest, and the existing CACHE_SCHEMA_VERSION pricing-behavior
  version. None of these moved the corpus fingerprint before, so a
  repricing fetch or pricing-logic fix could keep serving old rendered
  costs against an unchanged session corpus.

- The optimize path (default; --no-optimize unaffected) no longer
  reads or writes the persisted snapshot at all. Optimize findings
  depend on mutable project/config/prompt/hook state with no single
  enumerable fingerprint, and computing them already requires the same
  full parse the snapshot exists to skip, so excluding them from the
  snapshot loses no additional performance versus fingerprinting that
  input surface.

Adds two regression tests exercising both fixes end-to-end.
Version 0.9.21 across CLI, desktop app, and Windows menubar; refreshed
LiteLLM pricing snapshot and fallback (catches the 2026-08-24 OpenAI
GPT-5.6 cut and DeepSeek v4 repricing mid-rollout, getagentseal#1134 tracks
re-tightening); CHANGELOG stamped for 0.9.21; README release links to
0.9.21. Snapshot-pinned model tests updated to current catalog truth.
…ind an instant paint

The renderer memo was an LRU capped at 8-24 entries, so cycling periods
evicted payloads and returning to one forced a skeleton plus a 1.5-6s
recompute. The memo is now one latest payload per key for the app
session: a revisited period paints its cached numbers in the same
commit, skips the CLI entirely when the payload is under 30s old, and
otherwise revalidates behind the painted data and swaps in place.
Failed revalidates keep last-good data with the existing error surface;
first visits keep the loading state. (getagentseal#1135 part 1)
Ports the menubar's Kimi flow to the Electron quota module: read-only
~/.kimi-code/credentials (KIMI_CODE_HOME honored), api.kimi.com usages
snapshot, terminal expired-login state that never refreshes the CLI's
short-lived token, 5-hour/weekly window mapping with over-limit clamp,
membership plan label, and the standard per-provider Settings toggle.
… dirs, env-var invisibility

Round 3 adversarial-panel fixes for PR getagentseal#999:

- Snapshot writes now go through a per-queryKey file plus a CAS re-read
  guard before rename, closing the last-writer-wins race where a slower
  recompute against an older corpus could clobber a fresher save, and
  where distinct queryKeys used to evict each other's single shared slot
  (B-G1).
- cline-cli's discoverSessions now fingerprints the growing
  <sessionId>.messages.json sibling instead of the static
  <sessionId>.json metadata file, so new turns in a live session are no
  longer invisible to computeCorpusFingerprint (C-G1).
- computeCorpusFingerprint folds in computeEnvFingerprint per discovered
  provider, so non-discovery env vars like CODEBURN_CURSOR_MAX_BUBBLES
  and KIMI_MODEL_NAME can no longer serve a stale-forever snapshot (A-G1).
- collectFilesRecursive now resolves symlinked subdirectories and
  recurses into them (with a visited-inode guard against cycles) instead
  of misclassifying them as leaf files (C-G2).
- Added tests/session-cache-status-snapshot.test.ts covering concurrent
  writers against the snapshot file, mirroring session-cache-shards.test.ts's
  existing coverage for the main cache (D-G9).
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.

6 participants