Regression: tool_use parse failures jumped ~8x since CLI 2.1.165 ("The model's tool call could not be parsed")

Resolved 💬 1 comment Opened Jun 8, 2026 by libaoming Closed Jul 14, 2026

Summary

Since CLI 2.1.165, the rate of The model's tool call could not be parsed (retry also failed) errors has jumped roughly 8x compared to versions from a week earlier. The failure is mechanical and probabilistic — it hits all tools regardless of argument size or complexity (including a 68-character Read call whose entire input is a single file_path), so it is not a content/escaping problem on the model side. It looks like a regression in how the CLI accumulates/parses streamed input_json_delta for tool_use blocks.

When it happens the CLI sends "Your tool call was malformed and could not be parsed. Please retry.", retries once, and on a second failure surfaces the terminal retry also failed message — losing the turn.

Evidence (failure rate by version)

Measured over ~10 days of local session logs under ~/.claude/projects/**/*.jsonl, counting occurrences of "malformed and could not be parsed" divided by total messages tagged with each version:

| Version | Failure rate |
|--------:|-------------:|
| 2.1.156 | 1.85 ‰ |
| 2.1.159 | 3.07 ‰ |
| 2.1.162 | 3.49 ‰ |
| 2.1.165 | 16.40 ‰ |
| 2.1.167 | 13.64 ‰ |
| 2.1.168 | 14.15 ‰ |

The jump at 2.1.165 is sharp and sustained — a 4–8x regression versus the 2.1.156–162 baseline.

Ruled out (with data)

  • Output truncation (max_tokens) — 0 messages with stop_reason: "max_tokens" in the sample.
  • Context window exhaustion — at the moment of failure, context was median ~62k / max ~96k tokens, far below the 200k limit.
  • Large/complex tool arguments — failures span every tool type, including Read with a 68-char input. Not correlated with argument length.
  • A single "poisoned" message retried in a loop — failures are scattered across each session, not clustered in one turn.

→ Points to a probabilistic regression in client-side tool_use JSON parsing/streaming introduced around 2.1.165.

How to reproduce / measure

No deterministic single-call repro (it's probabilistic), but the regression is clearly visible by aggregating local logs:

import json, glob, collections
fail, total = collections.Counter(), collections.Counter()
for f in glob.glob("<CLAUDE_HOME>/projects/**/*.jsonl", recursive=True):
    for line in open(f, encoding="utf-8"):
        try: o = json.loads(line)
        except: continue
        v = o.get("version", "?")
        total[v] += 1
        c = o.get("message", {}).get("content")
        if isinstance(c, str) and "malformed and could not be parsed" in c:
            fail[v] += 1
for v in sorted(total):
    if fail[v]:
        print(v, f"{fail[v]/total[v]*1000:.2f} per-mille", f"({fail[v]}/{total[v]})")

Environment

  • Platform: macOS (arm64), native installer build (~/.local/share/claude/versions/)
  • Model: claude-opus-4-8 (default) across all the above versions
  • Affected versions: 2.1.165, 2.1.167, 2.1.168 (current latest at time of report)
  • Workaround in use: pinned back to 2.1.162 (claude install 2.1.162 + DISABLE_AUTOUPDATER=1), which restores the ~3‰ baseline.

Happy to share an anonymized per-version breakdown if useful.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗