Skip to content

[Bug]: generic_repeat loop detector never escalates to blocking — criticalThreshold has no effect #60111

@lykeion-dev

Description

@lykeion-dev

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

The generic_repeat loop detector only checks warningThreshold and always returns level: "warning". It never checks criticalThreshold, so the most common loop pattern (same tool, same arguments, repeated calls) can never be blocked — only warned. The documentation states criticalThreshold is "threshold for blocking repetitive loop patterns" but this does not apply to generic_repeat.

Steps to reproduce

  1. Enable loop detection with default thresholds (warningThreshold: 10, criticalThreshold: 20, globalCircuitBreakerThreshold: 30).
  2. Configure an agent that calls a non-polling tool (e.g., exec, read, web_fetch) with identical arguments repeatedly.
  3. Observe that after 10 calls, a warning is emitted. After 20 calls, another warning is emitted (not a block). The session continues indefinitely.
  4. Verify in source code (src/agents/tool-loop-detection.ts, function detectToolCallLoop) that the generic_repeat detector only checks warningThreshold and never references criticalThreshold.

Expected behavior

Per the documentation (docs/tools/loop-detection.md), criticalThreshold is described as "threshold for blocking repetitive loop patterns." The generic_repeat detector should escalate to level: "critical" (which blocks the tool call) when recentCount >= criticalThreshold, consistent with how known_poll_no_progress and ping_pong detectors behave.

Actual behavior

The generic_repeat detector only checks warningThreshold and always returns level: "warning". The criticalThreshold value is never referenced in the generic_repeat code path. The source code comment explicitly states // Generic detector: warn-only for repeated identical calls.

Detector behavior comparison:

Detector warningThreshold criticalThreshold globalCircuitBreaker
global_circuit_breaker ✅ Blocks (requires same args + same result)
known_poll_no_progress ⚠️ Warns ✅ Blocks
ping_pong ⚠️ Warns ✅ Blocks (with noProgressEvidence)
generic_repeat ⚠️ Warns ❌ Not checked ❌ Not checked

OpenClaw version

2026.3.24 (cff6dc9)

Operating system

Ubuntu 22.04.5 LTS (Linux 5.15.0-173-generic, x64)

Install method

npm global

Model

Not model-specific (affects all models — this is a gateway-level loop detection issue)

Provider / routing chain

N/A (gateway-level, before provider routing)

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

  • Affected: All users with loop detection enabled who encounter non-polling tool loops (the most common runaway pattern)
  • Severity: High — the primary protection mechanism has a gap for the most common loop type
  • Frequency: Always — generic_repeat never blocks regardless of threshold settings
  • Consequence: Runaway token spend and session lockups. The global_circuit_breaker does not catch these loops because it requires identical results (not just identical arguments), so loops with varying output bypass it entirely. The only effect is a warning message injected into the agent's context, which the agent may ignore.

Additional information

Suggested fix: Add criticalThreshold escalation to the generic_repeat detector. When recentCount >= criticalThreshold, return level: "critical" instead of level: "warning". This aligns with the documented behavior and closes the protection gap.

Alternatively, update the documentation to clarify that criticalThreshold only applies to known_poll_no_progress and ping_pong detectors, and that generic_repeat is intentionally warn-only. However, this leaves a significant gap in loop protection.

Relevant source files:

  • src/agents/tool-loop-detection.tsdetectToolCallLoop() function, lines 344-360
  • docs/tools/loop-detection.mdcriticalThreshold field description

Config used during observation:

{
  "tools": {
    "loopDetection": {
      "enabled": true,
      "historySize": 30,
      "warningThreshold": 3,
      "criticalThreshold": 4,
      "globalCircuitBreakerThreshold": 5
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingbug:behaviorIncorrect behavior without a crash

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions