openclaw - ✅(Solved) Fix loopDetection: generic_repeat detector only emits warning, never blocks execution [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70546Fetched 2026-04-24 05:56:39
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1

Root Cause

In src/agents/tool-loop-detection.ts, detectToolCallLoop():

// generic_repeat branch — only ever returns "warning", never "critical"
if (!knownPollTool && resolvedConfig.detectors.genericRepeat && recentCount >= resolvedConfig.warningThreshold) {
    return { stuck: true, level: "warning", ... };
}

The only detectors that can return level: "critical" are:

  • global_circuit_breaker — requires noProgressStreak >= globalCircuitBreakerThreshold, but depends on resultHash matching which may not work correctly for exec tool results
  • known_poll_no_progress — only triggers for process tool with poll/log actions
  • ping_pong — requires alternating between two tools

For generic tools like exec that are called repeatedly with the same command and same result, only the generic_repeat detector fires, which caps at warning.

Fix Action

Fixed

PR fix notes

PR #70549: fix(loop-detection): escalate generic_repeat to critical at criticalThreshold

Description (problem / solution / changelog)

Problem

The generic_repeat detector in tool-loop-detection.ts only ever returns level: "warning", meaning it can never block execution — even when a tool has been called 20+ times with identical arguments and zero progress.

Every other detector (known_poll_no_progress, ping_pong, global_circuit_breaker, unknown_tool_repeat) properly escalates to level: "critical" at the configured criticalThreshold, which triggers the blocking path in pi-tools.before-tool-call.ts (line ~149):

if (loopResult.level === "critical") {
  return { blocked: true, reason: loopResult.message };
}

But generic_repeat only checks against warningThreshold and always returns level: "warning". The criticalThreshold config value exists and is read — it's just never compared against.

Impact

Sessions can loop 20+ times with identical tool calls (same tool, same arguments) without being blocked. The only backstop is the global_circuit_breaker at 30 calls — but that requires matching result hashes too, so it may never fire if the tool returns slightly different error messages each time.

This is a reliability and cost issue: runaway loops burn tokens and API calls for no progress.

Fix

Add a critical escalation branch to generic_repeat that fires when recentCount >= resolvedConfig.criticalThreshold (default 20), returning level: "critical" to block execution. The existing warning at warningThreshold (default 10) is unchanged.

The check order is: critical first, then warning — matching the pattern used by known_poll_no_progress and ping_pong.

Tests

  • New: escalates generic loops to critical at criticalThreshold — verifies level: "critical" and detector: "generic_repeat" at exactly CRITICAL_THRESHOLD (20) calls
  • New: keeps generic loops at warning between warningThreshold and criticalThreshold — verifies level: "warning" at CRITICAL_THRESHOLD - 1 (19) calls
  • All 32 tests pass (run via vitest.unit-fast.config.ts)

Changes

  • src/agents/tool-loop-detection.ts: +13 lines (critical branch before warning branch)
  • src/agents/tool-loop-detection.test.ts: +17 lines (split old test into two, added critical test)

Closes #70546

Changed files

  • src/agents/tool-loop-detection.test.ts (modified, +18/-1)
  • src/agents/tool-loop-detection.ts (modified, +19/-1)

Code Example

15:17:56  exec called 3 times with identical arguments  (warning)
15:24:59  exec called 4 times with identical arguments  (warning)
15:25:47  exec called 5 times with identical arguments  (warning)
...
15:31:10  exec called 10 times with identical arguments (warning)
...
15:42:07  exec called 20 times with identical arguments (warning)
15:42:55  exec called 21 times with identical arguments (warning)

---

// generic_repeat branch — only ever returns "warning", never "critical"
if (!knownPollTool && resolvedConfig.detectors.genericRepeat && recentCount >= resolvedConfig.warningThreshold) {
    return { stuck: true, level: "warning", ... };
}

---

if (!knownPollTool && resolvedConfig.detectors.genericRepeat) {
    if (recentCount >= resolvedConfig.criticalThreshold) {
        return { stuck: true, level: "critical", detector: "generic_repeat", count: recentCount, message: `...`, warningKey: `generic:${toolName}:${currentHash}` };
    }
    if (recentCount >= resolvedConfig.warningThreshold) {
        return { stuck: true, level: "warning", detector: "generic_repeat", count: recentCount, message: `...`, warningKey: `generic:${toolName}:${currentHash}` };
    }
}
RAW_BUFFERClick to expand / collapse

loopDetection: generic_repeat detector only emits warning, never blocks execution

Bug Description

The loopDetection.generic_repeat detector in tool-loop-detection.ts only ever returns level: "warning" — it never escalates to level: "critical", meaning it cannot block runaway tool calls.

Evidence

Today I observed an exec tool call looping 21 times with identical arguments and identical results. The loop detection system detected it at count=3, continued warning at every subsequent repetition, but never blocked:

15:17:56  exec called 3 times with identical arguments  (warning)
15:24:59  exec called 4 times with identical arguments  (warning)
15:25:47  exec called 5 times with identical arguments  (warning)
...
15:31:10  exec called 10 times with identical arguments (warning)
...
15:42:07  exec called 20 times with identical arguments (warning)
15:42:55  exec called 21 times with identical arguments (warning)

No critical or block action was taken. The session was only stopped when the user manually deleted the session file.

Root Cause

In src/agents/tool-loop-detection.ts, detectToolCallLoop():

// generic_repeat branch — only ever returns "warning", never "critical"
if (!knownPollTool && resolvedConfig.detectors.genericRepeat && recentCount >= resolvedConfig.warningThreshold) {
    return { stuck: true, level: "warning", ... };
}

The only detectors that can return level: "critical" are:

  • global_circuit_breaker — requires noProgressStreak >= globalCircuitBreakerThreshold, but depends on resultHash matching which may not work correctly for exec tool results
  • known_poll_no_progress — only triggers for process tool with poll/log actions
  • ping_pong — requires alternating between two tools

For generic tools like exec that are called repeatedly with the same command and same result, only the generic_repeat detector fires, which caps at warning.

Reproduction

  1. Configure tools.loopDetection.enabled: true with warningThreshold: 3
  2. Call exec with the same command repeatedly (e.g., a SQL query that returns identical results)
  3. Observe: loop detection logs warnings at count >= threshold, but never blocks execution
  4. Tool continues to be called indefinitely

Expected Behavior

generic_repeat should escalate:

  • warning at warningThreshold (current behavior)
  • critical at criticalThreshold (missing) — this should block execution with a message like "You have called exec N times with identical arguments and no progress. Stop retrying and answer without this tool."

Suggested Fix

Add a critical branch for generic_repeat in detectToolCallLoop():

if (!knownPollTool && resolvedConfig.detectors.genericRepeat) {
    if (recentCount >= resolvedConfig.criticalThreshold) {
        return { stuck: true, level: "critical", detector: "generic_repeat", count: recentCount, message: `...`, warningKey: `generic:${toolName}:${currentHash}` };
    }
    if (recentCount >= resolvedConfig.warningThreshold) {
        return { stuck: true, level: "warning", detector: "generic_repeat", count: recentCount, message: `...`, warningKey: `generic:${toolName}:${currentHash}` };
    }
}

Environment

  • OpenClaw: 2026.4.21 (f788c88)
  • Model: bailian/qwen3.6-plus
  • Config: tools.loopDetection.enabled: true

extent analysis

TL;DR

The generic_repeat detector in tool-loop-detection.ts should be modified to escalate from a warning to a critical level when a certain threshold is exceeded, blocking execution to prevent infinite loops.

Guidance

  • Review the detectToolCallLoop() function in src/agents/tool-loop-detection.ts to understand the current logic for detecting and handling tool call loops.
  • Add a criticalThreshold configuration option to define when the generic_repeat detector should escalate to a critical level.
  • Implement the suggested fix by adding a critical branch for generic_repeat in detectToolCallLoop(), as shown in the provided code snippet.
  • Test the updated generic_repeat detector with the reproduction steps to verify that it correctly escalates to a critical level and blocks execution when the threshold is exceeded.

Example

if (!knownPollTool && resolvedConfig.detectors.genericRepeat) {
    if (recentCount >= resolvedConfig.criticalThreshold) {
        return { stuck: true, level: "critical", detector: "generic_repeat", count: recentCount, message: `You have called ${toolName} ${recentCount} times with identical arguments and no progress. Stop retrying and answer without this tool.`, warningKey: `generic:${toolName}:${currentHash}` };
    }
    if (recentCount >= resolvedConfig.warningThreshold) {
        return { stuck: true, level: "warning", detector: "generic_repeat", count: recentCount, message: `...`, warningKey: `generic:${toolName}:${currentHash}` };
    }
}

Notes

The provided fix assumes that the criticalThreshold configuration option is defined and properly configured. Additionally, the message property in the critical level response should be customized to provide a clear and informative error message.

Recommendation

Apply the suggested fix by modifying the detectToolCallLoop() function to include the critical branch for

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix loopDetection: generic_repeat detector only emits warning, never blocks execution [1 pull requests, 1 participants]