Server-side tool (server_tool_use) emitted but no result returned; response reports stop_reason: end_turn
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_useblock returns its result in that response and reportsstop_reason: tool_use(as the 2 successes did). - Actual (12×): the
server_tool_useblock is present, no result is produced, and the response reportsstop_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 inautocalls 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: trueanywhere in the transcript, no abort/error records; queued-message records are unrelated in time. - Model failing to emit the call — the
server_tool_useblock 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 byusage(input_tokens/output_tokensis not response-unique — it repeats across responses). - The
stop_reasonfinding 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.
- Investigate why a
server_tool_useblock is emitted in a response that produces no server-tool result and finalizes asstop_reason: end_turn. - If a server tool can't be serviced, return an explicit error result block rather than a tool-block-without-result +
end_turn.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗