Repository navigation
perf(renderer): bound the feed-debug ring by bytes - #723
Merged
Merged
Conversation
The two-day performance journal shows one session's feed-debug ring at 136 MB while its live transcript window was 28 MB. Record the bounded design before touching code. Refs #722 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk16MNVJtWAnCeGxGRuHqa
The per-session feed-debug ring was capped at 500 entries but not by size. Feed's `visible_rows` record carries one object per feed item, so on a long transcript each record is hundreds of KB and the two-day performance journal shows one session's ring at 136 MB — five times its live transcript window — for an always-on diagnostic. Estimate each entry's JSON size once (WeakMap-cached, like the live entry window) and evict from the head until the ring fits a 4 MiB budget. The newest entry always survives so an oversized record cannot blank the panel. Disk persistence keeps the full stream; only the in-memory ring shrinks. Fixes #722 Refs #375, #103 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk16MNVJtWAnCeGxGRuHqa
…asurable entries Review follow-ups: the module comment overstated disk persistence (it is best-effort, one in-flight batch per session, and the byte budget narrows the loss window); the memory gauge still re-sampled the ring every tick when the budget already caches an exact per-entry estimate; a payload that estimateJsonBytes cannot measure could ride under the budget for free, so it is now charged a conservative floor. Tests pin minimal eviction and that an oversized record is the next append's first casualty. Refs #722 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk16MNVJtWAnCeGxGRuHqa
Owner
Author
|
Self-review pass (adversarial review of the first cut) — resolved in HEAD:
Side effect worth noting: the pending batch shipped over IPC by |
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
SessionRuntime.feedDebugLog, the per-session ring behind FeedDebugPanel, is capped at 500 entries but not by bytes. Feed'svisible_rowsrecord (Feed.tsx) carriesdata.rowswith one object per feed item, emitted on every rendered-row change, so on a long transcript each record is hundreds of KB. The two-day performance journal (renderer.session.memory.feedDebugLog, 133,564 samples) shows one session's ring at 136,132,028 bytes — about five times that session's live transcript window (28 MB) — for a diagnostic that is always on. Details in #722.What changed
appendFeedDebugLognow also enforcesFEED_DEBUG_LOG_MAX_BYTES(4 MiB per session). Each entry's JSON size is estimated once and cached per entry object in aWeakMap, the same shape asestimateEntryBytesinliveEntryWindow.ts, and the ring is trimmed from the head until it fits.estimateFeedDebugLogBytesis exported for tests and future gauge use.Small records are unaffected: 500 × a few hundred bytes never approaches the budget. Only large payloads are bounded, which is exactly the case that needed it.
Design notes
useFeedDebugPersist→main/storage/feedDebugLog.ts) keys off entry ids and is unchanged; the id counter is not affected by eviction. As before, an entry evicted before the single in-flight append IPC resolves is not written; the byte budget narrows that window for very large records. That is the existing count-cap trade-off, now documented in the module comment.visible_rowspayload itself (dropping or windowingrows) would change what debug bundles contain and is left as a separate decision.Linked issues
Fixes #722. Refs #375 (same lesson applied to the live entry window), #103, #365.
Verification
NODE_ENV=test npx vitest run --project unit src/renderer/src/session-runtime/feedDebug.test.ts src/renderer/src/session-runtime/entries.test.ts— 23 tests passed. New contracts: count cap keeps the newest 500 with monotonic ids; small entries are never evicted by the byte rule; 300 KB records are trimmed to the budget from the head with contiguous newest ids; an oversized newest entry survives alone.npx tsc -b --pretty false— clean.feedDebugLogbytesEstimate gauge should stay ≤ ~4 MiB per session on the next long run.Plan:
docs/superpowers/plans/2026-09-03-feed-debug-ring-byte-cap.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01Kk16MNVJtWAnCeGxGRuHqa