Catch short-phrase loops, counter/emoji floods, and zero-width floods - #539
Merged
TheGreatAxios merged 5 commits intoAug 23, 2026
Merged
Conversation
Holes in degenerate-output detection let model loops burn to the token
limit (verified from live traces; 670K wasted streamed chars observed):
- Short-phrase loops: a live subagent emitted a 10-char unit
("Groaning. ") ~1,363 times; the 16-char window floor and the
watchdog's 24-char period floor both missed it. Lower the floors to 8
and raise the repeat thresholds 2x/3x so the minimum exactly-periodic
span stays at 128 / 192 chars.
- Counter/timestamp/fence/emoji floods: incrementing counters are never
byte-periodic, and "0% ", "}\n", "```\n", "🤔 " units fall under the
plain window floor. Add a second, folded pass over text streams
(windowMinChars 2, repeatThreshold 64, maxFoldedPeriodChars 16) —
the period cap refuses prose-shaped folds (numbered lists, tables).
- Zero-width floods: detectRepetition strips invisibles before the
periodicity check, so a wall of U+200C/U+200D normalizes to a short
healthy string. Add a contentless-growth guard that flags a raw-growth
window (2048 chars) whose visible content stays under 32 chars, wired
into the subagent run-loop abort path with its own abort reason.
…ase-loops-and-zero Amp-Thread-ID: https://ampcode.com/threads/T-01a02c68-0d8d-777b-b717-81fb9a282023 Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a02c68-0d8d-777b-b717-81fb9a282023 Co-authored-by: Amp <amp@ampcode.com>
…ase-loops-and-zero
…ase-loops-and-zero Amp-Thread-ID: https://ampcode.com/threads/T-01a02c68-0d8d-777b-b717-81fb9a282023 Co-authored-by: Amp <amp@ampcode.com>
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.
Two holes in degenerate-output detection let model loops burn to the token limit, plus additional flood shapes surfaced by the trace audit (670K wasted streamed chars, gpt-5.5 the worst offender).
Short-phrase loops
A live subagent emitted
"Groaning. "(10-char unit) ~1,363 times; neither detector could see it.src/subagent/repetition.ts:windowMinChars16 → 8,repeatThreshold8 → 16. Minimum exactly-periodic span stays at 8 × 16 = 128 chars."- item\n"normalizes to 7 chars and stays under the floor; 6-row table separators, 3 identical code lines, and repeat(4) prose stay far below 16 consecutive repeats.src/tui/stall-watchdog.ts:REPETITION_MIN_PERIOD24 → 8,REPETITION_MIN_REPEATS8 → 24 (3x, in step with the 3x-lower floor; minimum span stays at 192 chars).REPETITION_MIN_DISTINCT_CHARSuntouched —"Groaning. "has 9 distinct chars and triggers; the burst-of-"x" fixture still does not.Counter / timestamp / fence / emoji floods
Incrementing counters (
14279 14280 …,5620/5620. 5621/5621. …) are never byte-periodic;"0% ","}\n", "```\n","🤔 "units fall under the plain window floor. Added `DEFAULT_TEXT_FOLDED_REPETITION_CONFIG` — a second, digit-folded pass over text streams (`windowMinChars` 2, `repeatThreshold` 64, `maxFoldedPeriodChars` 16). The 16-char folded-period cap refuses prose-shaped folds (numbered lists and tables fold to ~30–50 char periods), and the 64-repeat bar keeps a legitimate short enumeration ("print 1..40") untripped. Fixtures cover all observed shapes, including the surrogate-pair emoji unit.Zero-width floods
normalize()strips invisibles before the periodicity check, so streams of thousands of U+200C/U+200D (observed 500–53,000 per stream in 35+ subagent partials) normalized to a short healthy string and were never flagged. AddedtrackContentlessGrowth(pure reducer inrepetition.ts): flags any 2048-raw-char window whose visible (invisible-stripped, whitespace-removed) content stays under 32 chars. Wired into the subagent run-loop abort path (src/subagent/run.ts) for both text and thinking deltas, with a distinct abort reason naming invisible/contentless output; it shares the repetition salvage path so the parent still gets the evidence tail.Checks
typecheck,build, andtestall pass (5,132 tests). Thelintstep ofbun run checkfails identically on cleanorigin/main(main's CI lint job is currently red; ~600 pre-existing prettier violations) — not addressed here to avoid a repo-wide reformat.Deferred
Keeping degenerate text out of persisted turn history is follow-up scope and deliberately not attempted here.
Fixes CL-6902