[Bug] Anthropic API Error: Opus 4.8 returns thinking-only responses without tool_use or text blocks
Bug Description
Bug: Opus 4.8 returns thinking-only responses (no text/tool_use) — a second failure mode of the same regression
described in issue #68529
> Related issue: https://github.com/anthropics/claude-code/issues/68529 (2026-06-15) describes fingerprint A on
Opus 4.8: stop_reason=stop_sequence + empty/minimal output_tokens. This report describes fingerprint B:
stop_reason=tool_use|end_turn + 800–1400 output_tokens + content blocks contain only thinking (no text, no
tool_use). Both fingerprints are observed on Opus 4.8 on 2026-06-15, both produce the same client-side malformed symptom, and both vanish on switching to Opus 4.7. They appear to be two failure modes of one
and could not be parsed
underlying regression in 4.8's tool-call / extended-thinking interaction layer.
## Symptom
Claude Code session abruptly halts with Your tool call was malformed and could not be parsed. Please retry. followed
by a second identical failure on retry, then the session stops. Reproducible across multiple sessions over the past 2
weeks on Opus 4.8 specifically.
## Root cause (confirmed from session jsonl, not speculation)
Some Opus 4.8 responses return content blocks that contain only a thinking block — no text, no tool_use. The
client cannot parse this as a valid tool call and surfaces the malformed-parse error. The response still has high
output_tokens (800–1400) and a normal stop_reason (tool_use or end_turn), so this is NOT max_tokens
truncation.
This is distinct from the fingerprint described in #68529 (where the response is essentially empty with
stop_reason=stop_sequence). The two fingerprints coexist on Opus 4.8 on the same day.
## Evidence (single session, 2026-06-15)
Running the detection script (below) on one session jsonl that contains both Opus 4.8 and Opus 4.7 responses:
```
total assistant messages = 42
orphan thinking-only messages = 4
msg_01BhXU4bG6YAoGQFQXTxU41L model=claude-opus-4-8 tokens=[REDACTED]
msg_01H6eLGmwCcc2UpDoFxeYBAA model=claude-opus-4-8 tokens=[REDACTED]
msg_01JPee8nTi9V7THbfkiD7pTH model=claude-opus-4-8 tokens=[REDACTED]
msg_01NbniykjTW7T6yHPqP8JLpu model=claude-opus-4-8 tokens=[REDACTED]
```
Key facts from the same session:
- All 4 orphans are on
claude-opus-4-8. Zero orphans onclaude-opus-4-7. - The 3rd orphan (1270 tokens) was immediately followed by the client-side
malformed and could not be parsedretry
prompt; the 4th orphan (1357 tokens) is that retry — same model again returned thinking-only.
- After switching to
/model claude-opus-4-7[1m]mid-session, every subsequent response on 4.7 containedtext
and/or tool_use as expected — no orphans.
- A concurrent session on the same machine, running entirely on Opus 4.7, produced 0 orphans across all responses.
This rules out network, proxy, payload-size, language, and effort-tier factors.
stop_reasondistribution across the full session contains onlytool_use/end_turn. **Nomax_tokensat
all.** Maximum output_tokens observed was ~3600 — nowhere near any output ceiling.
- This particular jsonl happened not to contain a fingerprint-A occurrence. Per #68529, fingerprint A is still
reproducing on 2026-06-15 — both fingerprints coexist; A has NOT been fixed.
## Repro environment
- Claude Code CLI on WSL2 (Ubuntu), Windows host
- Model:
claude-opus-4-8[1m](1M context tier) - Effort: high (also seen on default; effort tier appears irrelevant)
- Context length when the orphans occurred: ~50–100k tokens
- Extended thinking: enabled (all observed orphans contain a non-empty thinking block; not yet tested with extended
thinking disabled — worth investigating since the failure terminates exactly at the end of the thinking block)
- Multiple MCP tools active during the session — orphans are not correlated with any specific tool call
## Detection script
Aggregates assistant messages by message.id and flags any whose content contains a thinking block but no text
and no tool_use. This is specifically the detector for fingerprint B; the detector for fingerprint A (looking for
stop_reason=stop_sequence + zero/minimal output_tokens) is described in #68529:
```python
import json
from collections import defaultdict
path = '<session>.jsonl' # path to a Claude Code session jsonl
by_id = defaultdict(lambda: {'kinds': [], 'model': '?', 'tokens': [REDACTED]})
for line in open(path, encoding='utf-8', errors='replace'):
try: o = json.loads(line)
except: continue
if o.get('type') != 'assistant': continue
m = o.get('message', {})
mid = m.get('id')
if not mid: continue
…
Note: Content was truncated.