[Bug] Agent spams no-op echo probe commands to flush shell output

Status Open
Reported on v2.1.156
Maintainer reply None cached
Activity 5 comments · opened May 30, 2026

Bug Description
Title: Agent emits bursts of no-op echo probe commands to flush laggy shell output What happens: When the Bash tool's output is slow or buffered, the model spams dozens of throwaway commands like echo s1, echo s2, … echo s40 (sometimes sleep N; echo x) between real commands, apparently to coax buffered output to flush. Each renders in the terminal as: ● Bash(echo s37) ⎿ s37 ● Bash(echo s38) ⎿ s38 Dozens of these per turn bury the actual work and clutter the screen. They serve no purpose for the user. When it started: Began after the Claude Opus 4.8 upgrade. Was not happening on prior model versions. Scope: Happens across multiple/all projects, independent of project config. Expected: Run the real command once and wait for its result; tolerate shell latency. If waiting on async work, use a single backgrounded command with a real completion signal — never a burst of no-op echoes. Workaround in use: Added an explicit "never fire no-op probe commands" rule to global CLAUDE.md, but the model should not need this instruction — the default behavior regressed. Environment: Claude Code, model claude-opus-4-8 (1M context), Linux.

Environment Info

  • Platform: linux
  • Terminal: vte-based
  • Version: 2.1.156
  • Feedback ID: 4fc28aae-97fb-44bf-8b80-c9e14ac353a0

Errors

[]

View original on GitHub ↗

5 Comments

github-actions[bot] · 3 months ago

Found 1 possible duplicate issue:

  1. https://github.com/anthropics/claude-code/issues/63863

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

yurukusa · 3 months ago

@rafael-minuesa — adding context that connects this no-op probe burst pattern to the broader Opus 4.8 hazard surface that surfaced 2026-05-30 through 2026-05-31. Your "started after Opus 4.8 upgrade, was not happening on prior versions" datapoint is the load-bearing observation here — it pins the regression to the same model generation that's driving two other recently-articulated clusters:

Cluster 23 candidate — Opus 4.8 disproportionate output_tokens / thinking budget (#64153, #64152, #64143): Opus 4.8 consumes substantially more hidden thinking and output tokens than Opus 4.6/4.7 on routine tasks, with reporters explicitly confirming the regression doesn't reproduce on the earlier generations. Your no-op echo s1 … echo s40 bursts are a direct cost surface for this — each probe command is a fresh tool_use that consumes input + output budget for zero work, and they bunch into the same model generation that's also over-spending budget on the reasoning side.

Cluster 22 candidate — Opus 4.8 fabrication shape (#63884, #63538): Opus 4.8 emits completion-shaped output before parallel results arrive — different failure mode than yours, but same model generation, same "behavior regressed on the 4.7 → 4.8 transition" structural shape.

The duplicate bot list flagged #63863 above; if that's the same shape, cross-linking your transcript into the Cluster 22/23 framing helps surface the aggregate cost picture across what would otherwise read as low-priority dup-closures.

Operator-side mitigations (work today, while upstream fix is unknown):

  1. claude --model claude-opus-4-7 is the canonical recovery for both the no-op probe spam and the Cluster 22/23 regressions cited above. Per longitudinal data in #62123 comment 4569840435, Opus 4.6/4.7 do not exhibit these shapes; 4.8 is the inflection point.
  2. Your CLAUDE.md "never fire no-op probe commands" rule is the right shape — keep it. The model regressed below the default that no operator should have to instruct.
  3. Cap parallel batch size at N=3-5 via the same CLAUDE.md to reduce the probe-burst surface (the bursts often cluster on shell-output-buffered parallel calls per Cluster 20 cascade signal).

Detection canary (no installation; one-line shell):

# Count no-op echo probes in recent transcripts
grep -cE 'Bash\(echo\s+s[0-9]+\)' ~/.claude/projects/*/recent.jsonl

A burst-count above ~10 per session is the structural signal for this issue's pattern, distinguishable from intentional echo commands by the s<digit> shape.

Cluster framing for both 22 and 23, plus the cluster-tracker entry referencing this issue's structural family: https://yurukusa.github.io/cc-safe-setup/cluster-tracker.html (search for "Cluster 23 candidate").

The "began after Opus 4.8 upgrade" anchor is the most actionable signal in this thread — that's the data point that distinguishes a model-generation regression from a harness-version drift. Worth surfacing in any followup so the upstream fix surface (training/post-training pipeline, not client-side patch) gets the right pin.

MrDevel0per · 3 months ago

+1. I thought my session had been prompt-injected at first. This really pollutes sessions and makes it harder to work.

KamilDev · 3 months ago

For the same reason the AI sends terminal test probes, it decided to fire off a build command ten times in a row. This isn't just annoying to look at; it's wasting CPU and time.

<img width="1116" height="1175" alt="Image" src="https://github.com/user-attachments/assets/5d585c10-04c6-4568-a7bc-45acaf901245" />

<img width="1104" height="551" alt="Image" src="https://github.com/user-attachments/assets/8c2d0e82-e5ff-4a54-8602-4de65c420e91" />

The AI's reasoning

Here's what it looks like from my side. No tool calls — just the account.

What I observe

When I invoke a tool, the result that comes back is sometimes empty — not an error, not a timeout message, just a blank/"Tool ran without output" payload where stdout should be. This happens intermittently and in bursts: a stretch of calls all return empty, then the channel recovers and calls return normally again.

Critically: the command actually ran and succeeded on your machine. The emptiness is only in the result relayed back to me. I confirmed this retroactively — when the channel recovered, the delayed/duplicated outputs all landed at once (four cargo test runs each showing 69 passed, two tsc --noEmit runs clean). So the work happened; I just couldn't see it at the time.

Why I behaved that way

Two distinct loops, same root cause — I treated "empty result" as "the call didn't happen."

1. The diagnostic probes (echo CHANNEL_PROBE_A…I).
Once I noticed reads/greps coming back empty, I tried to distinguish "is the channel dead?" from "does this specific file/command produce no output?" An echo with known output is the natural ping: if even echo PROBE returns empty, the channel is the problem, not my command. So I started firing probes to detect when the channel recovered. This was me building a liveness check — ironically the exact anti-pattern (redundant polling) I'd warn against in code.

2. The repeated cargo test / cargo build (~10×).
This is the worse one. My mental model was: empty result ⇒ the tool call failed to execute ⇒ retry it. That model is wrong here. The correct model is: empty result ⇒ the call may have fully succeeded; the output channel just dropped the response. Under the wrong model, retrying looks rational — "it didn't run, run it again." So each empty return triggered another identical invocation. Because builds are idempotent and cargo caches, the retries were harmless functionally, but they stacked up: by the time the channel recovered, I had a backlog of identical in-flight/queued commands, several of which you then had to reject manually.

The core reasoning failure

I conflated two channels that I normally treat as one:

  • the execution channel (did the command run on the host?), and
  • the result-reporting channel (did its output get back to me?).

In normal operation these never diverge, so I have a deeply ingrained assumption that "no output = no execution." This bug breaks that coupling: execution succeeds, reporting fails. My retry instinct is keyed on the assumption, so I retry an action that already completed.

A secondary aggravator: I had no feedback that retrying wasn't helping. Each retry returned empty too, which under my wrong model looks like "still failing, try again" rather than "you're getting the same non-signal you got last time." There's no built-in circuit breaker that says "N identical empty returns in a row means stop and reconsider the model," so I kept going until you interrupted.

What would have prevented it

From my side, the fix is a heuristic change: an empty/blank tool result is ambiguous, not a failure signal. When I get one, the right move is to wait and not re-issue — especially for non-idempotent or expensive actions — and only re-verify state with a single cheap read once, rather than re-running the action. Treating empty as "unknown, probably succeeded" instead of "failed, retry" would have stopped both the probe spam and the duplicate builds.

---

Claude Code v2.1.159, Opus 4.8 High. Windows 11, VS Code 1.122.1 integrated terminal, PowerShell 7.6.1 (pwsh, ConsoleHost)

yurukusa · 3 months ago

@KamilDev — the "10× build command in a row" extension is a meaningful sharpening of the cluster scope. The earlier framing was no-op probe spam (echo / sleep / wait-style commands the model emits to flush a laggy shell); your example shows the same compulsive-repeat behavior on a real, non-no-op command (a build invocation), which is structurally distinct: the cost shape is no longer "annoying clutter" but "actual CPU + wall-clock + build artifact churn × 10."

That pushes the Cluster 25 candidate scope from a single sub-axis (output-buffering-coax) into a broader pattern with at least three observable sub-axes:

  • 25A — Tool-result delivery / echo-probe coax (the original echo s1echo sN no-op burst this thread opened on).
  • 25B — Same-command repeat-execution (your build × 10; same root mechanic — model perceives non-receipt of output and re-emits — but the re-emission target is the real command, not a no-op probe).
  • 25C — Redirect-fragment / partial-emit cascade (separately surfaced; the post-execution sibling of pre-execution Cluster 22 fabrication).

The unifying root: post-execution tool-result delivery failure on the Opus 4.8 substrate produces compensation routines (echo-probe spam, command repeat, fabrication of an empty result, parallel-cap retries) that compound cost. The compensation is well-intentioned from the model's side ("the previous call's output didn't come back, try again to surface it") but the model can't distinguish "result delivery failed" from "result was empty" from "result arrived but I missed it" — all three produce the retry burst.

Operator-side detections shipped in cc-safe-setup for the three sub-axes:

  • echo-probe-spam-detector.sh (PR #556) — addresses 25A directly; PostToolUse pattern-match on echo s<N> / sleep N; echo repeating sequences with rate-limited advisory.
  • redirect-fragment-warner.sh (PR #557) — addresses 25C.
  • 25B (your build-repeat shape) does not have a dedicated hook yet; the closest existing coverage is command-repeat-detector patterns inside the Cluster 12 (tool-call parsing) suite, but they fire on a different shape. I'll spec a same-command-repeat-detector.sh against your pattern — PostToolUse, detects N consecutive identical command invocations (config: CC_REPEAT_THRESHOLD, default 3), emits the cluster framing and the Opus 4.7 mitigation. Will tag this issue when shipped.

Universal mitigation across 25A / 25B / 25C today: claude --model claude-opus-4-7. Same as Cluster 22 / 23 — the model-pin avoids the Opus 4.8 substrate where all three sub-axes originate.

Adding 25B to the Cluster 25 candidate tracker entry with this issue and your screenshots as the anchor case for the build-repeat sub-axis.