Commit graph

6 commits

Author SHA1 Message Date
Resham Joshi
151509a1fb
fix(cache-lock): coalesce heartbeat ticks; make time-sensitive tests deterministic (#1559)
* fix(cache-lock): coalesce heartbeat ticks so the fence never waits behind a backlog

Each interval tick queued a full heartbeat in the owner serializer, so an
interval firing faster than a tick completes grew the queue without bound and
verifyStillOwner waited behind all of it. The 1ms-heartbeat fence test hit its
30s timeout on a loaded runner and retried up to 10 times, which is the silent
cache-lock suite hang in CI.

* test(menubar): hold off the helper's SIGTERM until the stand-in app records its status

The relaunch helper signals the app as soon as the CLI child is reaped, which
could land between the stand-in's fopen and fclose and leave an empty status
file. Also wait for the recorder's full line instead of a fixed sleep.

* test: pin day-boundary tests so they pass at any time of day

CLI children that query "today" now run in a fixed-offset zone where it is
about noon on the UTC date (tests/fixtures/noon-tz.ts), so fixtures a little in
the past are always today; the old clamp to UTC midnight put them in the future
for the first minutes of the day and zeroed the counts. The month-scoped budget
and project-filter fixtures get the same treatment (the latter also failed on
days 1-5 of every month), the cursor lookback case derives its start from now
instead of a date that ages out on 1 Oct, and the renderer tests that format a
stamp from a few hours back pin the clock to mid-day. The session drawer date
is formatted in the local zone, so its expectation is too.

* test: derive dated fixtures from the clock so rolling windows never age them out

The six-month --period all window and the sync ledger's six-month prune
dropped fixtures fixed at April-July 2026 from 1 Nov 2026 on. CLI fixtures now
sit 30 days back (utcDaysAgo), the ledger and push tests pin Date to July 2026,
the --to-only range uses yesterday, and the date-picker tests pin mid-month so
two days back is on the calendar they open (they failed on the 1st and 2nd of
every month). The noon-tz helper moves to tests/fixtures/clock.ts.

* test(desktop): give spawned stand-ins room to boot before the silence window

The watchdog and graceful-kill tests used 1-1.5s silence windows, and the
window before a child's first byte includes node's boot. On a loaded machine
the child was signalled before it installed its handler or wrote its byte.
The windows are now 3s and the last-byte test measures from the byte the child
stamps. The killAll and fallback-scheduler tests wait on their condition with
a 15s deadline instead of a 2-3s one.
2026-09-25 05:06:43 +02:00
iamtoruk
62cb4f8630 test: isolate the real home on Windows and make the CLI suite pass there
os.homedir() reads USERPROFILE on Windows and never HOME, so the
isolation setup and every test that moved home were still resolving the
developer's real home directory. Fixtures came back empty, real sessions
leaked into counts, and the code under test wrote plans, receipts and a
sync ledger into the real ~/.config/codeburn during a run.

USERPROFILE now joins the redirected variables and a shared setHome()
helper moves HOME and USERPROFILE together; every spawn env carries both.
The remaining Windows-only failures were POSIX-shaped fixtures: chmod
tricks that only toggle a read-only attribute on NTFS, pid 1 as an
always-alive lock holder, splitting paths on '/', bare tar, mode-bit
expectations, and per-file timeouts too short for the cost of a tsx spawn.
Each gets a platform-aware equivalent so nothing is skipped on Windows and
the POSIX branches stay byte-for-byte what they were.

Full run on Windows: 267 files passed, 2 skipped, 3592 tests passed.
2026-09-02 13:43:57 -07:00
Resham Joshi
d34f32ec87
feat(serve): resident serve child reuses status snapshots across requests (#1135 part 2) (#1146)
* feat(cli): cache the menubar-json base payload on the optimize path (#1135)

The status snapshot keyed on a corpus fingerprint (#999) currently
excludes the default optimize path: persisting scanAndDetect's
findings alongside the base payload would pin a moment-in-time render
of the mutable, non-fingerprinted inputs the corpus fingerprint cannot
observe (settings.json, CLAUDE.md, defined skills/agents/commands,
MCP config) under a key with no way to invalidate them. That is the
exact defect commit 113ebb1c excluded the optimize path to avoid.

This change picks option (b) from the #1135 plan: the snapshot now
covers BOTH optimize=true and optimize=false (the queryKey already
carries optimize as a boolean, so the two are naturally partitioned
without a separate cache namespace). On a hit, the base payload is
served from disk and only the optimize block is re-derived fresh
through a new computeOptimizeBlock helper (re-running scanAndDetect
over a fresh parseAllSessions of the unchanged corpus). On a save,
the optimize block is stripped from the persisted record and
replaced with an empty placeholder, so a mutation in the
non-fingerprinted inputs between two requests at the same fingerprint
is reflected in the second response even though the rest of the
payload is byte-identical to the first. The save gate from #999 is
preserved verbatim (payload.stale !== true, payload.hydration ===
undefined, isSessionHydrationComplete()), the resident codeburn serve
in-memory output memo already keys on the full argv so the two
optimize values land in different memo entries, and the desktop
app's buildOverviewArgs has always sent the menubar-json query
without --no-optimize, so the new hit rate lights up with zero app
changes. (#1135)

* fix(cli): revert optimize-path snapshot reuse, re-validate snapshots on load (#1135)

Post-build review fix round on the optimize-path snapshot change:

1. Snapshot reuse is no longer attempted for one-shot optimize polls.
   One-shot codeburn status invocations behave exactly as on main:
   fresh compute, fresh generated timestamp. The pre-existing
   --no-optimize one-shot save-gate behavior is unchanged.

2. The corpus-change test now holds --provider constant across both
   calls, so the only delta is the appended session line and the test
   proves a corpus change is picked up, not a queryKey change.

3. loadStatusSnapshot now mirrors the save gate: a persisted record
   whose payload carries stale === true or a hydration block is treated
   as a miss and recomputed. Belt-and-braces only; the save gate that
   prevents such records is unchanged.

4. The optimize block cannot be re-derived on a snapshot hit without
   the parsed corpus. scanAndDetect consumes the full ProjectSummary
   trees plus a transcript scan plus ~/.claude state, so re-deriving it
   pays the full fresh parse the snapshot exists to avoid on exactly
   the cold processes the disk snapshot serves, and the resident serve
   child gains nothing because its in-memory output memo already
   dedupes a repeated argv. Deriving from warm caches is therefore not
   honestly achievable, so this takes the plain exit: optimize-carrying
   requests are excluded from reuse (useSnapshot = !queryScope.optimize
   everywhere, the main semantics) and the computeOptimizeBlock helper,
   the save-time optimize stripping, and the optimize-path snapshot
   tests are reverted. Honesty over cleverness.

The CHANGELOG entry this branch added now describes the final
semantics. Full suite green, tsc clean.
2026-08-25 10:13:43 -07:00
iamtoruk
e0a2b41308 fix(status-snapshot): never persist a degraded parse as an authoritative snapshot
The save gate re-read the module-level hydration global at a point the
codebase itself documents as unsafe: buildMenubarPayloadForRange runs
further parses (its own 365-day history re-parse included) after
capturing the verdict, and the last parse wins. A poll that went
read-only under a held refresh lock could therefore persist its
under-reported payload once a later parse reset the global, pinning
degraded dollars to disk until the corpus next changed. The gate now
requires the payload's own markers (stale !== true, no hydration
block), captured at the one safe read point, with the global kept as a
belt-and-braces conjunct. Regression holds the refresh lock, asserts
no snapshot file is written for the degraded pass, and that the next
clean pass writes one. Post-merge review follow-up to #999.
2026-08-25 02:28:47 -07:00
ozymandiashh
e4f7615b59 fix: harden status snapshot invalidation and writes 2026-08-25 01:24:19 +03:00
Doug Gabehart
46f2ddd2bc fix: close status-snapshot race, cline-cli fingerprint gap, symlinked dirs, env-var invisibility
Round 3 adversarial-panel fixes for PR #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).
2026-08-24 15:05:02 -05:00