Repository navigation
Memory janitor: condense only when it can shrink, parse the reply it gets, name a trust-dialog block - #33
Merged
Conversation
added 2 commits
September 30, 2026 14:14
…ing to show Production evidence: the memory-reflection condense pass aborted far more often than it succeeded, and every attempt still spent a full Haiku hidden-session call for zero shrink. Five root causes, fixed at the source (the retry/prompt/JSON fixes are defense-in-depth; the parser and keep-budget fixes are the structural root causes): - not-smaller (the largest class): the verbatim "keep" region was bounded by a fixed section COUNT only, so a handful of untouched recent sections could outweigh anything a summary of the rest could ever save. selectKeep() now also bounds the keep region by bytes (keepBudgetPct), guaranteeing eviction headroom. A file that still fails not-smaller is backed off (must regrow by 10% before the autonomous scan retries it) instead of being retried every tick for the same zero-shrink outcome. - no parseable JSON: parseSummary()/extractJsonObject() now finds the JSON object under preamble, trailing prose, or a fence not anchored to the whole string, via a real balanced-brace scan (honors quoted strings) instead of a string-anchored regex. - recent-count-mismatch: a model-echoed "## " sub-heading inside the summary re-parsed as a new section boundary on the next load, corrupting the 3-region structure. demoteEmbeddedHeadings() sanitizes both the condensed text and hoisted pinned lines before they're ever written. - no assistant response found in transcript: retried once (the likely transcript-flush race) before aborting. - hidden session timed out: NOT retried (replaying the same oversized prompt buys nothing); instead the timeout budget now scales with the eviction payload size instead of a fixed 180s regardless of how much text is being summarized. Tightened the prompt contract (no "## " lines, no prose outside the JSON) as an additional line of defense alongside the parser/sanitizer fixes. test/reflect.test.cjs reproduces each abort class against a real memory.md shape and proves the fix; full suite + both typecheck targets green with the pre-existing failure set byte-for-byte unchanged (diffed against a clean clone of the same base commit).
…ent no-response race Live verification of the condense-retry fix (against a real, previously untrusted cwd) reproduced 'no assistant response found in transcript' deterministically, not as a rare race: a hidden headless session spawned in a cwd Claude Code has never been trusted in blocks forever on the first-run workspace-trust dialog. The boot-quiet heuristic reads the rendered dialog as "ready for input" and sends the condense prompt into a menu that never consumes it, so no transcript is ever written — every attempt hits the identical dialog and fails identically. Confirmed with a raw node-pty capture against this exact repo checkout: the dialog's own CLI flags (--permission-mode bypassPermissions, --dangerously-skip-permissions) do not suppress it, and it renders with absolute-column cursor moves between words rather than spaces, so a naive substring match against the raw PTY bytes never fires either — the detector strips ANSI/CSI sequences and all whitespace before matching. hiddenClaude.ts now distinguishes the two: a genuine no-response case keeps its retryable message; a detected trust-dialog block gets a distinct, non-retried one (reflect.ts's retry loop only matches the original phrase), so a guaranteed-to-repeat failure no longer burns a second hidden-session call. Verified against three independent live spawns: two unmodified showed the generic message in ~3.5-6.5s (too fast for genuine model latency, and unaffected by widening idleMs/bootCapMs to 12s — ruling out a timing race), one post-fix correctly returned the new trust-dialog message. Full suite + both typecheck targets green; failure set byte-for-byte unchanged against the pre-existing baseline.
🚫 This PR is missing its before/after evidenceEvery pull request here has to show its work. Screenshots or a short screen recording, before the change and after it.
How to fix it: edit the description, keep the A bug fix with no visible surface still needs it: show the failing behaviour, then the same steps passing. A terminal recording is fine. Genuinely nothing to show — a CI tweak, a typo, a dependency bump? A maintainer can apply the |
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.
Stops the memory janitor from spending tokens with nothing to show.
Since 08-25 the condense step aborted 855 times against 67 successes. Four failure classes, each reproduced against real or captured fixtures and fixed at its root:
recentKeepwas a fixed section count with no byte cap, so a few untouched recent sections outweighed anything a summary could save; on real files the old rule evicted 0 and 1 sections.selectKeep()is now byte-budgeted (keepBudgetPct, 25%), and a file that just failed not-smaller is not retried until it has regrown 10%.##line was re-parsed as a phantom section on the next load; embedded headings are demoted.no assistant response found in transcript (~284) reproduced deterministically: a headless PTY session in a directory Claude Code has not trust-accepted blocks on the first-run workspace-trust dialog, which renders with cursor-positioning escapes so a naive substring check never matches.
looksLikeTrustDialog()(ANSI-strip, whitespace-strip, substring) now reports it as a distinct, non-retried error, so the log names the real cause going forward. Whether the production hidden-spawn directory is trusted for headless sessions is a separate question the new log line will answer.Verification: 26 new tests (
test/reflect.test.cjs,test/hidden-claude-trust-dialog.test.cjs); full suite and both typecheck targets green; the pre-existing failing set is identical to a clean checkout of the base.