Skip to content

Abort a model that loops on repeated output - #350

Merged
TheGreatAxios merged 4 commits into
mainfrom
cl-5577-repetition-guard
Aug 7, 2026
Merged

Abort a model that loops on repeated output#350
TheGreatAxios merged 4 commits into
mainfrom
cl-5577-repetition-guard

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Detects repetition by finding the smallest exact-repeating character period in the streamed tail, so a loop with no line breaks between cycles (the captured incident's shape) is caught the same way a line-level loop is
  • Requires enough repeats and character variety in the repeating unit to rule out ordinary structure (a repeated table row, a few identical code lines, a monochrome run) as degeneration, verified against each
  • Throttles the check to run once per chunk of new text rather than per token, and makes the idle notice consult the repetition flag so a looping-but-not-silent stream is never labeled a silent hang

Verification

  • bun run typecheck passes
  • bun run build passes
  • bun run test passes (3916 pass, 0 fail), including regression tests for the incident string with no newlines, a repeated markdown table row, repeated code lines, and a monochrome character run

Closes CL-5577

@linear-code

linear-code Bot commented Aug 7, 2026

Copy link
Copy Markdown

CL-5577

@TheGreatAxios
TheGreatAxios force-pushed the cl-5577-repetition-guard branch 2 times, most recently from f41f407 to 2d0825a Compare August 7, 2026 08:13
The stall watchdog measured silence, so a model streaming the same
line on repeat never tripped it: tokens kept arriving, lastActivityAt
kept refreshing, and the run burned tokens until a human noticed and
interrupted it. The watchdog now also folds streamed text into a
bounded buffer and checks it for a line repeated past a threshold,
aborting immediately on detection regardless of how fast the loop is
producing output. The recovery message names it as the model
repeating itself and reports the tokens spent on the looped span, so
a retry reads as reasonable rather than papering over a hang.
Line-splitting missed the captured incident outright: the looping
sentences ran together with no newline between them, so the whole
span collapsed into one line and never reached the occurrence check.
Detection now finds the smallest period the streamed tail exactly
repeats, which catches the no-newline shape the same way it catches a
line-level loop.

Line counting also flagged ordinary structure — a repeated markdown
table row, a few identical code lines — as degeneration. The period
search requires more repeats and enough character variety in the
repeating unit to rule those out, verified against both plus a
monochrome run (a repeated rule or the same character streamed many
times), which is trivially periodic at every length and would
otherwise be the easiest false trigger of all.

The per-delta check is now throttled to run once per chunk of new
text rather than once per token, since a repeating tail cannot appear
or disappear between two three-character tokens. The idle notice also
now checks the repetition flag directly, so a stream that is looping
but not silent can no longer be labeled a silent hang.
A tool call ended a streaming cycle but left the repetition buffer
intact, so several short narration lines said before separate tool
calls in one turn concatenated into an apparent loop and aborted an
otherwise ordinary turn. A genuinely degenerate model repeats within
one unbroken stream; narration between tool calls does not, so the
buffer now clears whenever a tool call begins.
Clearing the repetition buffer on every tool call fixed the narration
false positive but went too far: a model that loops while interleaving
even a no-op tool call between repeats was no longer caught at all,
since nothing carried across the reset. Keep a cheap fingerprint of
each completed cycle instead of its raw text, and flag only once
several consecutive cycles fingerprint alike. Narration varies enough
cycle to cycle to clear that bar; an unvarying repeated block does not,
and now trips within a small, bounded number of cycles rather than
never.
@TheGreatAxios
TheGreatAxios force-pushed the cl-5577-repetition-guard branch from 2d0825a to bcc3bc8 Compare August 7, 2026 08:24
@TheGreatAxios
TheGreatAxios merged commit 284b998 into main Aug 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant