Skip to content
DocumentationReferencev1.2.1

Loop & churn detection

Two independent signals for recurring diagnostics and repeated file-rule checks.

Signalint records diagnostic history across MCP checks. It can reveal a recurring issue pattern, but it does not detect arbitrary conversation loops or know whether an agent’s strategy is correct.

A signature combines the rule and a normalized message template. Quoted text, certain identifier patterns, numeric details, whitespace, and case are normalized; the signature is not tied to a specific file or line.

Signalint counts appearances separated by absence. A loop warning is emitted after a signature has disappeared and reappeared at least twice:

Check 1 Check 2 Check 3 Check 4 Check 5
present → absent → present → absent → present
warning

The warning is structured:

{
"signature": "TS2322:type <identifier> is not assignable to type <identifier>.",
"occurrences": 2,
"hint": "This issue was fixed and reappeared 2 times — consider a different approach"
}

occurrences counts reappearances after the initial appearance. Once a signature has crossed the threshold, it remains in the retained historical loop state; a clean check does not erase that historical count.

A separate counter tracks the same (file, rule) pair across check_files calls, even if the message or line changes. It warns at 3 or more consecutive matching file-check results:

{
"file": "src/auth.ts",
"rule": "TS2345",
"checkCount": 3,
"hint": "src/auth.ts has re-triggered TS2345 across 3 separate checks — the agent may be stuck on this file, not just this exact issue"
}
EventChurn behavior
Pair appears in a check_files resultIncrement once for that call
Pair is absent from a check_files resultReset its counter to zero
check_project is calledDoes not increment or reset the counter in the live process

Absence from a targeted result can reflect a narrower request, not necessarily a verified project-wide fix. Treat these signals as prompts to investigate, not proofs of causality.

Normal check responses include loopWarning or fileRuleChurnWarning only when active. In schema 1.4 inactive warnings are omitted, not emitted as null. For the complete flagged lists, call get_loop_status with {}:

{
"looping": false,
"signatures": [],
"fileChurning": false,
"fileRuleChurns": []
}

looping and fileChurning are independent. A file can churn without an exact signature disappearing and returning.

Session history is written under .signalint/session.jsonl. On restart, Signalint replays a bounded tail of valid entries and skips malformed or crash-truncated lines. The tail reader retains at most 5,000 non-empty log lines (also bounded by bytes), then skips invalid entries; fewer valid checks may be restored. The .1 backup is used by stats, not replayed by SessionMemory. The active log rotates before an append would exceed the 10 MiB budget; this is a rotation trigger, not a guarantee that one oversized entry fits.

Replay is not identical to live churn handling in v1: a project check logs an empty pair array and can reset previously active churn counters when that history is replayed. Historical warnings also depend on the retained tail, not unlimited lifetime history.

Stop the server before deliberately removing session history. Clearing only the SQLite cache does not clear recorded loop history.

Implementation: src/memory/sessionMemory.ts.