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.
Diagnostic oscillation
Section titled “Diagnostic oscillation”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 5present → absent → present → absent → present warningThe 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.
File-rule churn
Section titled “File-rule churn”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"}| Event | Churn behavior |
|---|---|
Pair appears in a check_files result | Increment once for that call |
Pair is absent from a check_files result | Reset its counter to zero |
check_project is called | Does 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.
Reading warnings
Section titled “Reading warnings”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.
Persistence and reset
Section titled “Persistence and reset”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.