Skip to content

perf(diagnostics): opt-in feed-debug persistence, no-op worktree index saves, documented daily profile - #1349

Merged
Juliusolsson05 merged 8 commits into
mainfrom
perf/diagnostics-default-off
Sep 27, 2026
Merged

Juliusolsson05 merged 8 commits into
mainfrom
perf/diagnostics-default-off

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Refs #767. This PR covers items 1, 3 and 7 of the acceptance line. Item 6, moving the diagnostic sinks into a utilityProcess, is split into #1346, so this is Refs, not Fixes.

Problem

Of #767's always-on diagnostic subsystems, three things remained on main:

  • feed-debug had no gate. Every session in every build shipped its ring to disk once a second, up to 128 MiB per session file and 22 % of the debug budget.
  • The worktree activity index was rewritten on every background refresh. Even when every candidate was a cache hit, it re-stringified the whole index (30 MB+ for heavy users) and replaced the file. The new updatedAt also invalidated the summary cache that exists to spare the 10 s UI poll.
  • Nothing documented a daily profile.

Change

  • feed-debug persistence is opt-in (useFeedDebugPersist): it runs only with AGENT_CODE_DEV_DEBUG=1 or the existing Persistent Aggressive Debug Logs setting.
    • The ring still records. Save Debug Logs reads the ring (runtime.feedDebugLog), not the file, so bug reports are unchanged; only the after-crash copy needs a switch.
    • Turning a switch on persists the retained tail on the next tick.
    • The setting's description now says it also keeps the feed-debug log on disk.
    • UNCONFIRMED: this reuses that setting rather than adding a new one.
    • Declined: the issue's "sampled ring in packaged builds". The ring is already bounded (500 entries and 4 MiB per session, perf(feed-debug): pace persistence appends by time instead of per runtimes replacement #750), and sampling would drop the consecutive rows a report needs.
  • Worktree index writes only when its content changed (WorktreeActivityIndex.refreshNow).
    • Content counts as changed when something was parsed or the on-disk key count moved. Otherwise the refresh records only lastIndexedAt, and updatedAt (the content generation that keys the summary cache) stays the same.
    • Every save trims the hot LRU to the on-disk key set. Before this, a deleted transcript stayed in light users' summaries and could return as an un-reparsed cache hit (review of perf(diagnostics): opt-in feed-debug persistence, no-op worktree index saves, documented daily profile #1349). With the trim, any key new to the index is a cache miss and gets parsed.
    • A transcript that keeps failing to parse no longer forces a rewrite.
    • saveWorktreeActivityIndex persists the caller's updatedAt instead of stamping a second timestamp.
  • README "Diagnostics flags": each flag and setting, with its default and cost. AGENT_CODE_PERF adds spans and traces; the Performance Monitor's baseline history runs regardless. The table also gives and the recommended daily profile (all off; DEV_DEBUG=1 only on demand). "Proxy-Streamed Semantic Rendering" is documented as the live transport, not a diagnostic. The issue's original profile listed it as "off", but turning it off blanks live output, and its disk growth is bounded by Claude proxy-events.jsonl grows to 2+ GB per session and cannot be pruned while live #1273's rotation.

Tests

  • WorktreeActivityIndex.test.ts (new; the class had no tests). It uses the real index store and transcript parser on a temp state dir; only home-directory discovery is replaced. There are 10 cases:
    • a no-change refresh leaves the file bytes and mtime unchanged and keeps updatedAt, while still recording the check;
    • a changed, an added and a deleted transcript each persist and advance updatedAt;
    • a same-count swap of paths persists;
    • served summaries change after a transcript moves to another worktree, and drop a removed transcript;
    • a provider-session change forces a re-parse;
    • an unparsable transcript does not rewrite the index;
    • disk and memory hold the same updatedAt.
  • indexStore.test.ts: the store keeps the caller's updatedAt, a deterministic check.
    • Fail-first: the no-change and same-generation cases fail on origin/main.
  • useFeedDebugPersist.renderer.test.tsx:
    • with both switches off, there is no append IPC and the ring still records;
    • nothing is written after persistence is switched off (the effect cleanup flushed once before);
    • either switch persists, including the tail recorded before it was switched on.
    • The existing cadence cases now enable dev-debug explicitly.
    • Fail-first: all 3 gate cases fail on origin/main.
  • Related renderer suites (feed-debug cursor reset, runtime isolation, orchestration runtime, persistence, spawn/handoff flows, first run): 100/100.
  • npx tsc -b: clean.

Plan: docs/plans/2026-09-26-diagnostics-default-off.md.

🤖 Generated with Claude Code

Juliusolsson05 and others added 3 commits September 26, 2026 19:23
…saves, daily profile (#767)

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The background refresh (at least every 60 s) re-stamped updatedAt,
stringified the whole index (30 MB+ for heavy users) and replaced the
file even when every candidate was a cache hit, and the new timestamp
invalidated the summary cache that exists to spare the 10 s UI poll.
A refresh now persists only if something was parsed, a parse failed or
the entry count moved; otherwise it records only when it checked. The
store persists the caller's updatedAt instead of stamping its own.
First tests for WorktreeActivityIndex (fail-first on origin/main).

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…are on; document the flags

The feed-debug ring was shipped to disk once a second for every session
in every build (up to 128 MiB per session file) with no switch. It is
now persisted only with AGENT_CODE_DEV_DEBUG=1 or the Persistent
Aggressive Debug Logs setting. The ring itself still records, and Save
Debug Logs reads the ring, so bug reports are unchanged; only the
after-crash copy needs a switch. Turning a switch on persists the
retained tail on the next tick.

The README gains a Diagnostics flags section: each flag and setting,
its default and cost, and the recommended daily profile (all off,
DEV_DEBUG only on demand). Proxy streaming is documented as the live
transport, not a diagnostic.

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05 Juliusolsson05 added type:perf Performance or resource use class:C6-unbounded Unbounded growth sev:P2 Real bug with a workaround labels Sep 27, 2026
Juliusolsson05 and others added 4 commits September 26, 2026 20:22
… switching persistence off

Review of #1349:
- "Nothing parsed and the same count" missed a swap (B leaves, C comes
  and goes, B returns unchanged from the LRU), so the stale set stayed on
  disk. The index now tracks its on-disk key set and persists when the
  set of paths differs.
- Turning feed-debug persistence off ran the effect cleanup's final
  flush, writing one more batch; the cleanup now flushes only while the
  switch is still on (a real unmount).
- Tests pin the in-memory generation through the served summaries and
  the provider-session cache guard (both mutations survived before).
- README: AGENT_CODE_PERF adds spans and traces; the Performance
  Monitor's baseline runs regardless. The command-palette text for the
  setting names the feed-debug log too.

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re's no-restamp

Review b of #1349: with the key-set comparison, a previously indexed
transcript that fails to parse already counts as a change (it leaves
the set); counting skipped files forced a full index rewrite on every
refresh while an unindexed file stayed unreadable. A store-level test
now pins that saveWorktreeActivityIndex keeps the caller's updatedAt
(the old test only caught a restamp when two Date.now() calls
straddled a millisecond).

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 2 of the #1349 review: the LRU only aged entries out, so for light
users (whose summaries are built from the LRU) a deleted transcript kept
appearing in the Worktrees summary, and one could return as a cache hit
without being re-parsed. Every save now trims the LRU to the on-disk key
set, which also makes any key new to the index a cache miss; the
explicit key-set comparison is therefore unreachable and is removed.

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he index

Review c of #1349 (round 2): re-adding the skipped-file trigger passed
every test. A discovered transcript that keeps failing to parse is not
index content; the new case keeps the file bytes and mtime unchanged
across a refresh with one skipped candidate.

Refs #767

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05

Juliusolsson05 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner Author

Review disposition (final head af6dae0a)

Rounds:

  • Round 1: a (codex), b (opencode) and c (codex) at bb5dfa4a. b was MERGE-READY; a and c were FIX-BEFORE-MERGE.
  • Round 2 (verification by a and c): c90fea0c. Both found the same new item (LRU retention). It was fixed in 764bdeb0, and c's remaining survivor was pinned in af6dae0a.
# Finding Verdict Disposition
R1-a major A same-count swap of transcript paths was not persisted valid Fixed (bf0e2b58, then at the root in 764bdeb0): the LRU is trimmed to the on-disk keys on every save, so a returning key is a cache miss and gets re-parsed. The swap test is kept.
R1-a/c minor Switching feed-debug persistence off still wrote one batch (effect cleanup) valid Fixed: the cleanup flushes only while the switch is on (enabledRef). A test checks for no append after switching off.
R1-a/c survivors The in-memory updatedAt and the provider-session cache guard were unpinned valid Fixed: served-summary and re-parse tests; both mutations are killed.
R1-b minor The skippedFiles term forced rewrites while a file stayed unreadable valid Fixed: the term is removed. A no-rewrite test (R2-c) catches the term being re-added.
R1-b minor The store's no-restamp was only covered by a millisecond race valid Fixed: a deterministic indexStore test.
R1-c minor The README over-claimed AGENT_CODE_PERF (the baseline Performance Monitor always runs) valid Fixed. The command-palette text now also names the feed-debug log.
R1-c tradeoff Default-off loses the after-crash feed-debug copy (1.1 GB on the owner's disk today) accepted Stated in the body. Save Debug Logs reads the in-memory ring. Reusing the "Persistent Aggressive Debug Logs" setting remains UNCONFIRMED; the owner may want a lighter dedicated switch.
R2-a major / R2-c minor A removed transcript stayed in light users' served summaries (the LRU only aged out) valid (pre-existing, exposed here) Fixed (764bdeb0): the LRU is trimmed to the on-disk keys on save. A served-summary removal test is red without it (2 failures).
R2-a survivor this.totalOnDisk = nextCount removal unpinned (first refresh with >1,000 transcripts) accepted Not pinned: it needs a >1,000-transcript fixture. The field drives the light/heavy summary path, and the change doesn't touch that logic.
R2 declined sessionRecovery.integration failed in combined runs under load separate Filed #1357. This PR changes nothing that file imports, and it passes 66/66 ×3 unloaded.

Verification at the final head:

  • src/main/worktreeActivity passes 13/13, and persistence tests pass.
  • npx tsc -b is clean (at c90fea0c; later commits change only tests and one method on a private class).

Final-pass cap (manager, 2026-09-27): round-2 a and c are manager-verified (temp/review-1349/manager-verify-a.md, -c.md): 764bdeb0 was checked fail-first, and af6dae0a is pinned.

Disclosed residual: round-2 a found a surviving mutation — deleting this.totalOnDisk = nextCount (WorktreeActivityIndex.ts) left the index tests green.

  • It matters only on a FIRST refresh with more than 1 000 transcripts. The light-user summary path would then iterate only the capped LRU instead of loading the full on-disk index.
  • It is not pinned by a test, and is accepted as a residual.

@Juliusolsson05

Copy link
Copy Markdown
Owner Author

OWNER-APPROVED: diagnostics feed-debug persistence opt-in (default off) is accepted.
Owner, 2026-09-27T07:33:05Z: "just make these for me, just keep the fixtures in, they are innocent enough. do not delete stuff often" (manager applied it).

@Juliusolsson05
Juliusolsson05 merged commit 3120036 into main Sep 27, 2026
2 checks passed
Juliusolsson05 added a commit that referenced this pull request Sep 27, 2026
@Juliusolsson05
Juliusolsson05 deleted the perf/diagnostics-default-off branch September 27, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

class:C6-unbounded Unbounded growth sev:P2 Real bug with a workaround type:perf Performance or resource use

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant