Server-side tool (server_tool_use) emitted but no result returned; response reports stop_reason: end_turn

Resolved 💬 2 comments Opened May 31, 2026 by SpoonJimSquareSocks Closed Jul 3, 2026

Summary

A server-side tool (advisor) was invoked 14 times in a single session. Twice it worked (response stop_reason: tool_use, an advisor_tool_result returned). The other 12 times the assistant response contained a server_tool_use block named advisor but reported stop_reason: end_turn and returned no tool result — the tool never ran and the turn ended silently.

A response that emits a server_tool_use block yet finalizes as stop_reason: end_turn with no corresponding result is self-inconsistent: a server_tool_use block should accompany stop_reason: tool_use. That self-inconsistency is the bug.

User-visible symptom: the assistant says it's calling the tool, then just stops — repeated 12× in one session, including 5× within a single user turn.

Environment

  • Claude Code CLI: 2.1.158
  • Model: claude-opus-4-8 (1M-context variant)
  • OS: Linux 6.18 (aarch64)
  • Tool: a no-parameter server-side tool (server_tool_use); its result is produced API-side within the same response

Evidence (grouping-independent)

Reading each record that carries a server_tool_use block, by its own stop_reason:

| stop_reason on the tool-call record | result returned? | count |
|---|---|---|
| tool_use | yes | 2 / 2 |
| end_turn | no | 12 / 12 |

Zero exceptions across the session. This is read per-record and does not depend on how response blocks are grouped.

Important caveat (for accuracy): end_turn is a correlate, not a proven cause of non-execution. In the same anomalous responses, client-side tool_use blocks (e.g. Bash, Read, Edit) also carried stop_reason: end_turn and did execute and return results. So end_turn alone does not block tool execution — it co-occurs specifically with the server-side tool's missing result. The reportable anomaly is the combination: server_tool_use block present + no server-produced result + end_turn.

Expected vs actual

  • Expected: a response containing a server_tool_use block returns its result in that response and reports stop_reason: tool_use (as the 2 successes did).
  • Actual (12×): the server_tool_use block is present, no result is produced, and the response reports stop_reason: end_turn. Silent — no error surfaced to the user.

What we ruled out (so you don't chase these)

All falsified by live experiment or by timestamp in the originating session:

  • Permission mode auto — a separate session running in auto calls the same tool successfully.
  • Mid-session settings re-read / mode transition — forcing a settings re-read then calling the tool resolved normally.
  • A user-installed Stop hook / settings edits — these postdate the first failure by 23+ minutes; the failures predate them.
  • User interrupting a slow call — no interrupted: true anywhere in the transcript, no abort/error records; queued-message records are unrelated in time.
  • Model failing to emit the call — the server_tool_use block is present all 14 times; the model emitted it every time.

Leading open direction (not asserted)

Within the affected session the failures begin as a hard flip after one success and never recover, while the same tool works in other sessions — consistent with server-side, session-scoped state, but unproven.

Notes for whoever re-checks a transcript

  • Group response blocks by the API response id (message.id), not by usage (input_tokens/output_tokens is not response-unique — it repeats across responses).
  • The stop_reason finding above is read per-record and is grouping-independent.

Impact & ask

Silent failure: the model re-emits the call and may invent false self-explanations, wasting whole turns. Any server-side tool could be affected the same way.

  1. Investigate why a server_tool_use block is emitted in a response that produces no server-tool result and finalizes as stop_reason: end_turn.
  2. If a server tool can't be serviced, return an explicit error result block rather than a tool-block-without-result + end_turn.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗