Skip to content

fix: extend loopDetection to cover exec tool calls (#34574)#52126

Closed
poushwell wants to merge 6 commits into
openclaw:mainfrom
poushwell:fix/loop-detection-exec
Closed

fix: extend loopDetection to cover exec tool calls (#34574)#52126
poushwell wants to merge 6 commits into
openclaw:mainfrom
poushwell:fix/loop-detection-exec

Conversation

@poushwell

Copy link
Copy Markdown

What: loopDetection did not escalate to critical for repeated exec calls. 121 identical shell commands = zero critical alerts.

Why: exec is the most dangerous tool type. The generic repeat detector was warn-only for non-poll tools, so criticalThreshold never triggered for exec.

Changes:

  • Added escalation path for exec tool in loop detection: repeated identical exec calls now trigger critical at criticalThreshold (same as other dangerous patterns)
  • Default thresholds unchanged (warningThreshold: 3, criticalThreshold: 6)
  • No change to enabled/disabled defaults
  • 4 new tests: repeated exec alert, different commands no alert, below threshold no alert, existing non-exec unchanged

Testing:

  • vitest run src/agents/tool-loop-detection.test.ts — all passed
  • Existing loop detection behavior unchanged for non-exec tools

Closes #34574

exec is the most dangerous tool type but was not covered by loop
detection. 121 identical shell commands produced zero alerts.

Extended loop detectors to include exec tool calls. Same thresholds
(repeatThreshold: 3, criticalThreshold: 6) now apply to exec.

Closes #34574

Made-with: Cursor
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Mar 22, 2026
@greptile-apps

greptile-apps Bot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends detectToolCallLoop so that repeated identical exec calls escalate to critical at criticalThreshold, filling a gap where the generic repeat detector was warn-only for non-poll tools. The implementation is correct: a new guard checks isExecTool && recentCount >= criticalThreshold before the existing generic warning block, giving exec a warning at warningThreshold and a critical at criticalThreshold — consistent with how known_poll_no_progress is structured.

  • The logic in tool-loop-detection.ts is sound and consistent with existing detector patterns.
  • The warningKey format (generic:${toolName}:${currentHash}) is shared between the new critical block and the existing warning block, which is consistent with how known_poll_no_progress handles the same situation.
  • The four new tests cover warning, below-threshold, different-args, and non-exec regression cases — but no test verifies the core new behavior: exec reaching criticalThreshold returning level: "critical". This is the stated purpose of the fix and should have explicit test coverage.

Confidence Score: 4/5

  • Safe to merge; the implementation is correct and the gap is a missing test for the critical path, not a logic error.
  • The implementation correctly adds exec critical escalation before the existing generic warning block, consistent with how other detectors handle warning/critical tiers. The only gap is that none of the four new tests actually exercises count >= criticalThreshold for exec to confirm the critical return — a straightforward addition that would bring this to 5/5.
  • src/agents/tool-loop-detection.test.ts — missing a test asserting exec returns critical at criticalThreshold
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/tool-loop-detection.test.ts
Line: 290-307

Comment:
**Missing test for the critical escalation path**

The primary behavior this PR adds — exec calls escalating to `critical` at `criticalThreshold` — has no test. The test named "warns for repeated exec calls at repeat threshold" only covers the warning branch (`count: 3` equals `warningThreshold: 3`). A test that passes `count: 6` (equal to `criticalThreshold`) and asserts `level === "critical"` is needed to verify the core fix described in the PR.

```suggestion
    it("warns for repeated exec calls at repeat threshold", () => {
      const result = detectLoopAfterRepeatedCalls({
        toolName: "exec",
        toolParams: { command: "ls /tmp" },
        result: {
          content: [{ type: "text", text: "file-a\nfile-b" }],
          details: { status: "completed", exitCode: 0 },
        },
        count: 3,
        config: execThresholdConfig,
      });

      expect(result.stuck).toBe(true);
      if (result.stuck) {
        expect(result.level).toBe("warning");
        expect(result.detector).toBe("generic_repeat");
      }
    });

    it("escalates exec to critical at criticalThreshold", () => {
      const result = detectLoopAfterRepeatedCalls({
        toolName: "exec",
        toolParams: { command: "ls /tmp" },
        result: {
          content: [{ type: "text", text: "file-a\nfile-b" }],
          details: { status: "completed", exitCode: 0 },
        },
        count: 6,
        config: execThresholdConfig,
      });

      expect(result.stuck).toBe(true);
      if (result.stuck) {
        expect(result.level).toBe("critical");
        expect(result.detector).toBe("generic_repeat");
        expect(result.message).toContain("CRITICAL");
      }
    });
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: extend loopDete..."

Comment on lines +290 to +307
it("warns for repeated exec calls at repeat threshold", () => {
const result = detectLoopAfterRepeatedCalls({
toolName: "exec",
toolParams: { command: "ls /tmp" },
result: {
content: [{ type: "text", text: "file-a\nfile-b" }],
details: { status: "completed", exitCode: 0 },
},
count: 3,
config: execThresholdConfig,
});

expect(result.stuck).toBe(true);
if (result.stuck) {
expect(result.level).toBe("warning");
expect(result.detector).toBe("generic_repeat");
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing test for the critical escalation path

The primary behavior this PR adds — exec calls escalating to critical at criticalThreshold — has no test. The test named "warns for repeated exec calls at repeat threshold" only covers the warning branch (count: 3 equals warningThreshold: 3). A test that passes count: 6 (equal to criticalThreshold) and asserts level === "critical" is needed to verify the core fix described in the PR.

Suggested change
it("warns for repeated exec calls at repeat threshold", () => {
const result = detectLoopAfterRepeatedCalls({
toolName: "exec",
toolParams: { command: "ls /tmp" },
result: {
content: [{ type: "text", text: "file-a\nfile-b" }],
details: { status: "completed", exitCode: 0 },
},
count: 3,
config: execThresholdConfig,
});
expect(result.stuck).toBe(true);
if (result.stuck) {
expect(result.level).toBe("warning");
expect(result.detector).toBe("generic_repeat");
}
});
it("warns for repeated exec calls at repeat threshold", () => {
const result = detectLoopAfterRepeatedCalls({
toolName: "exec",
toolParams: { command: "ls /tmp" },
result: {
content: [{ type: "text", text: "file-a\nfile-b" }],
details: { status: "completed", exitCode: 0 },
},
count: 3,
config: execThresholdConfig,
});
expect(result.stuck).toBe(true);
if (result.stuck) {
expect(result.level).toBe("warning");
expect(result.detector).toBe("generic_repeat");
}
});
it("escalates exec to critical at criticalThreshold", () => {
const result = detectLoopAfterRepeatedCalls({
toolName: "exec",
toolParams: { command: "ls /tmp" },
result: {
content: [{ type: "text", text: "file-a\nfile-b" }],
details: { status: "completed", exitCode: 0 },
},
count: 6,
config: execThresholdConfig,
});
expect(result.stuck).toBe(true);
if (result.stuck) {
expect(result.level).toBe("critical");
expect(result.detector).toBe("generic_repeat");
expect(result.message).toContain("CRITICAL");
}
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tool-loop-detection.test.ts
Line: 290-307

Comment:
**Missing test for the critical escalation path**

The primary behavior this PR adds — exec calls escalating to `critical` at `criticalThreshold` — has no test. The test named "warns for repeated exec calls at repeat threshold" only covers the warning branch (`count: 3` equals `warningThreshold: 3`). A test that passes `count: 6` (equal to `criticalThreshold`) and asserts `level === "critical"` is needed to verify the core fix described in the PR.

```suggestion
    it("warns for repeated exec calls at repeat threshold", () => {
      const result = detectLoopAfterRepeatedCalls({
        toolName: "exec",
        toolParams: { command: "ls /tmp" },
        result: {
          content: [{ type: "text", text: "file-a\nfile-b" }],
          details: { status: "completed", exitCode: 0 },
        },
        count: 3,
        config: execThresholdConfig,
      });

      expect(result.stuck).toBe(true);
      if (result.stuck) {
        expect(result.level).toBe("warning");
        expect(result.detector).toBe("generic_repeat");
      }
    });

    it("escalates exec to critical at criticalThreshold", () => {
      const result = detectLoopAfterRepeatedCalls({
        toolName: "exec",
        toolParams: { command: "ls /tmp" },
        result: {
          content: [{ type: "text", text: "file-a\nfile-b" }],
          details: { status: "completed", exitCode: 0 },
        },
        count: 6,
        config: execThresholdConfig,
      });

      expect(result.stuck).toBe(true);
      if (result.stuck) {
        expect(result.level).toBe("critical");
        expect(result.detector).toBe("generic_repeat");
        expect(result.message).toContain("CRITICAL");
      }
    });
```

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ce51d9129

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 475 to 477
const recentCount = history.filter(
(h) => h.toolName === toolName && h.argsHash === currentHash,
).length;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use consecutive streak for exec critical blocking

The new critical path keys off recentCount, which is the total number of matching exec calls anywhere in the history window, not a consecutive or no-progress streak. Because this commit turns that condition into a hard block, a session can now be stopped after six reused commands even when those calls are interleaved with other work or producing different outputs (for example, periodic exec checks during a long task). This creates false-positive blocks in normal workflows; the critical branch should require consecutive/no-progress evidence before blocking.

Useful? React with 👍 / 👎.

@steipete

Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex review.

Current main already ships a broader, safer loop-detection implementation that blocks repeated identical no-progress outcomes for any tool, including exec, while intentionally keeping generic repeats warn-only until there is stronger evidence of a stuck loop. That makes this PR's exec-specific recentCount hard block redundant and less safe than the shipped behavior.

What I checked:

  • Global breaker already blocks runaway identical no-progress tool loops: detectToolCallLoop() computes noProgressStreak and returns a critical global_circuit_breaker result once it reaches globalCircuitBreakerThreshold. This path is tool-agnostic, so it applies to exec as well as other tools. (src/agents/tool-loop-detection.ts:457, bc73141e8284)
  • Main intentionally keeps generic repeats warn-only: The generic repeat branch is still documented and implemented as warn-only below the global breaker. That directly conflicts with this PR's approach of turning repeated exec reuse into an earlier hard block based on recentCount alone. (src/agents/tool-loop-detection.ts:541, bc73141e8284)
  • Regression test codifies the warn-only policy below the breaker: Current tests explicitly assert that generic repeated loops stay at warning even at CRITICAL_THRESHOLD, confirming that the shipped policy is to require stronger evidence than simple repeat count before hard-blocking. (src/agents/tool-loop-detection.test.ts:320, bc73141e8284)
  • Regression test proves any tool escalates to critical on repeated identical no-progress outcomes: Current tests cover the shipped fix path with blocks any tool with global no-progress breaker at 30, proving that the original symptom of many identical calls never reaching a critical block no longer holds on main. (src/agents/tool-loop-detection.test.ts:449, bc73141e8284)
  • Prior PR review identified a false-positive risk in the proposed change: A review comment on this PR noted that the proposed recentCount-based critical block would count non-consecutive reused exec commands and could stop normal workflows. The shipped main implementation instead keys hard blocking off repeated no-progress outcomes, which is the safer contract. (cfdd9ec23c2a)
  • Shipped release contains the same loop-detection implementation: The current tool-loop-detection files match the v2026.4.23 tag; a diff against tag commit a9797214338ba31c52c796adbb75afb16e0684a9 was empty for these files, so this behavior is already released. (src/agents/tool-loop-detection.ts:457, a9797214338b)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review notes: reviewed against bc73141e8284; fix evidence: release v2026.4.23, commit a9797214338b.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

loopDetection does not catch repeated exec tool calls

2 participants