Blank stdout tool call at end of turn triggers false 'no visible output' retry loop

Status Open
Maintainer reply None cached
Activity 5 comments · opened Jun 23, 2026

Summary

When a response ends with a Bash tool call that produces no stdout (e.g. afplay /System/Library/Sounds/Hero.aiff -v 0.7 &), Claude Code's rendering system incorrectly detects 'no visible output' for the turn and auto-injects a retry prompt: [Your previous response had no visible output. Please continue and produce a user-visible response.]

This causes the model to regenerate the same response, resulting in the user seeing identical responses 2-3 times in a row.

Steps to Reproduce

  1. In any Claude Code session, end a response with a Bash tool call that produces no stdout output
  2. Example: last action is afplay /System/Library/Sounds/Hero.aiff -v 0.7 & (background process, no stdout)
  3. Observe: the system injects a 'no visible output' retry prompt
  4. The model regenerates, producing a duplicate response
  5. If the regenerated response also ends with the same no-stdout Bash call, it loops again (confirmed up to 3 duplicates)

Expected Behavior

A turn that contains substantive text output followed by a tool call with no stdout should be considered to have visible output. The 'no visible output' detection should check whether the turn contained any text content, not just whether the last tool call produced stdout.

Actual Behavior

The turn is flagged as having no visible output, triggering a retry. The user sees the same response 2-3 times.

Context

  • Platform: macOS (darwin 25.3.0)
  • Shell: zsh
  • The text content of the response IS visible to the user — only the final tool call produces no stdout
  • The issue is specifically triggered by tool calls with empty stdout, not by missing text output

Workaround

None available when the CLAUDE.md rules require a specific command as the last action of every response.

View original on GitHub ↗

4 Comments

serge-zol · 1 month ago

Root cause from string-inspecting the compiled bundle (claude-code 2.1.202, native build): the reporter's mechanism guess ("checks only the last tool call's stdout") isn't what fires. The actual detector keys on thinking-only turns, not tool stdout.

The injected string lives in a branch guarded by this condition (de-minified, variable names as-found):

if ( (stop_reason === "end_turn" || stop_reason === "stop_sequence")
&& !isApiError && mode !== "compact" && !<guardA> && !<guardB> && !<guardC>
&& !responseMessages.some(m =>
m.content.some(b => b.type === "text" && b.text.trim().length > 0))
&& !alreadyNudged ) {
telemetry("query_thinking_only_response", "nudged");
inject("[Your previous response had no visible output. Please continue and produce a user-visible response.]");
}

Key points:

  • "Visible output" is defined as at least one non-empty text block in the turn. Tool calls are not counted.
  • The trigger requires stop_reason === end_turn/stop_sequence. A turn that ends in a tool call has stop_reason === "tool_use" and is excluded — so the nudge does not fire on the tool-call turn itself. It fires on a subsequent thinking-only turn that ends with no text block. The telemetry name (query_thinking_only_response) and state flag (thinkingOnlyNudged) confirm this.

Same root cause, second manifestation — Dispatch / Cowork: there the user-facing channel is the SendUserMessage tool, which produces no text block. A legitimate "reply via SendUserMessage, then end" exchange leaves a thinking-only end-turn with no text → false nudge → the Dispatch system prompt re-interprets "produce visible output" as "call SendUserMessage again" → the same message is resent 3–5×.

Suggested fix: treat a turn as having visible output when it delivered user-facing content via a tool (notably SendUserMessage, and side-effecting bash), not only when a non-empty text block is present — i.e. suppress query_thinking_only_response when the current exchange already reached the user through a tool.

Repro (Dispatch): any turn whose only user-facing output is a SendUserMessage call, followed by an empty/thinking-only turn.

Sha-Bang · 1 month ago

Same bug via a different trigger: a turn ending on an MCP tool call with no visible output

We hit this via the Claude Agent SDK (claude-agent-sdk==0.2.111, bundled CLI), and the trigger is broader than empty Bash stdout — it's any turn whose final block is a tool call that produces no user-visible output, including custom MCP tools.

Setup: streaming input, include_partial_messages=True, a custom MCP tool (suggest_prompts) that sets suggested-reply chips and intentionally returns no user-facing text. Our system prompt instructs the model to call it as the last action of a turn (after it has already produced its full answer).

What happens: the model produces a complete, substantive text answer, then calls the MCP tool as the final block. The CLI flags the whole turn as "no visible output" and injects:

[Your previous response had no visible output. Please continue and produce a user-visible response.]

The model then regenerates its answer, so the user sees the response 2–3×. This fires on essentially every turn for us, because ending a turn on a silent side-channel tool is intentional in our agent design.

Root of it: the "did this turn produce visible output?" check appears to consider only the final block, ignoring the text (and any visible tool output) emitted earlier in the same turn.

Two asks:

  1. Fix the detection to count visible output emitted anywhere in the turn, not just the final block.
  2. Expose an opt-out (e.g. a query option like nudge_on_empty_turn=false) for agents that intentionally end turns on silent tool calls — there's currently no setting/env/SDK flag to disable this.

Environment: macOS (darwin arm64), claude-agent-sdk 0.2.111.

vrennat · 1 month ago

Dispatch/Cowork: this fires on every single turn

In Dispatch mode, the agent's only output channel is the SendUserMessage tool — plain text replies aren't rendered. So every legitimate response triggers the false nudge, since there's no text block in the response. The nudge looks like a new user turn, the agent responds, and the loop produces 5-15 duplicate messages before the agent catches on.

Messages deliver fine to the user's phone — confirmed via screenshots. The nudge is purely a false positive.

Suggested fix

The thinkingOnlyNudged detector should recognize SendUserMessage calls as visible output, or expose a setting like nudge_on_empty_turn = false so Dispatch sessions can opt out.

Workaround

Instruct the agent (via memory or CLAUDE.md) to stop responding after 1-2 phantom prompts. Can't prevent the nudge, but breaks the loop.

CSfromCS · 28 days ago

Please bump, still active and resending response 2-5x again and again. Repro and scenario as described multiple times above from various perspectives.

Showing cached comments. Read the full discussion on GitHub ↗