anthropics / anthropics/claude-code
[BUG] Regression in 2.1.266: client emits system message at an invalid position → API 400 "role 'system' must precede an 'assistant' message or end the array"
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### Preflight Checklist
- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
### What's Wrong?
Since v2.1.266, Claude Code intermittently builds a request whose `messages` array contains a `system`-role entry in a position the Messages API rejects. The turn fails with:
API Error: 400 messages.N: role 'system' must precede an 'assistant' message or end the array; the directive-only form (content: [] with output_config) is accepted at any position
N varies (7, 13, 16, 19, 138, 151). There is no way to avoid or recover from it other than retrying the turn.
This is a clean regression. I scanned every session transcript in ~/.claude/projects/**/*.jsonl and correlated the error against the `version` field on each entry:
- 28 distinct versions used before the regression, from 2.1.142 (2026-05-26) through 2.1.260 (2026-09-11): zero occurrences
- First occurrence: 2026-09-11 09:24:56 on 2.1.266 — the first day 2.1.266 appears in my logs
- Still occurring on 2.1.268 and 2.1.270 (2026-09-14)
Impact so far: ~48 failed turns across 14 sessions in 3 separate projects.
Possible lead: a large share of failures are preceded within a few transcript entries by client-generated system-subtype records — most often `system/stop_hook_summary`, also `system/turn_duration` and `system/away_summary`. Notably `stop_hook_summary` entries appear even though I have no hooks configured. That suggests these internal system records may be serialized into the outgoing messages array without the position check the API enforces. I can't confirm that from transcripts alone — it's a lead, not a diagnosis. Also common nearby: `queue-operation` (queued user messages) and `attachment` / `file-history-snapshot` records, so an interrupted or queued turn may be part of the trigger.
No deterministic repro — it appears mid-session during ordinary use across unrelated projects, roughly once every few dozen turns, not tied to any one project, skill, or tool. Retrying usually succeeds.
### What Should Happen?
The client should never place a `system`-role entry in a position the Messages API rejects — or should use the directive-only form (content: [] with output_config), which is valid at any position. The turn should complete normally instead of 400ing and being lost.
### Error Messages/Logs
```shell
API Error: 400 messages.7: role 'system' must precede an 'assistant' message or end the array; the directive-only form (content: [] with output_config) is accepted at any position
Observed occurrences (timestamp, version):
2026-09-11T09:24:56 2.1.266 <-- first
2026-09-11T09:25:51 2.1.266
2026-09-11T09:26:39 2.1.266
2026-09-11T09:27:07 2.1.266
2026-09-11T09:28:59 2.1.266
2026-09-11T09:34:51 2.1.266
2026-09-11T09:55:08 2.1.266
2026-09-11T12:31:52 2.1.266
2026-09-11T12:32:23 2.1.266
2026-09-11T12:39:19 2.1.266
2026-09-11T13:50:36 2.1.266
2026-09-11T14:10:46 2.1.266
2026-09-11T14:22:07 2.1.268
2026-09-11T14:23:28 2.1.266
2026-09-11T14:41:14 2.1.266
2026-09-14T08:09:19 2.1.266
2026-09-14T08:36:02 2.1.266
2026-09-14T08:54:23 2.1.266
2026-09-14T12:35:14 2.1.270
2026-09-14T13:30:31 2.1.266
2026-09-14T13:30:50 2.1.270
2026-09-14T13:35:07 2.1.266
2026-09-14T13:37:19 2.1.266
2026-09-14T13:37:28 2.1.266
2026-09-14T13:39:56 2.1.266
2026-09-14T13:40:21 2.1.266
```
### Steps to Reproduce
No deterministic repro — the failure appears mid-session during ordinary use, roughly once every few dozen turns, across unrelated projects, and is not tied to any one project, skill, or tool. Retrying the turn usually succeeds. What I can give you is an exact way to observe it and confirm the version boundary from any affected machine's own transcripts.
To observe:
1. Install 2.1.266 or later (reproduced on 2.1.266, 2.1.268, 2.1.270).
2. Use Claude Code in the desktop app normally — multi-turn sessions with tool use, queued messages, and interruptions. No special configuration; I have no hooks configured (settings.json contains only permissions, theme, skipWorkflowUsageWarning).
3. Within a few dozen turns a turn fails with:
API Error: 400 messages.N: role 'system' must precede an 'assistant' message or end the array; the directive-only form (content: [] with output_config) is accepted at any position
To confirm the version boundary from transcripts (this is how I established it):
cd ~/.claude/projects && find . -name '*.jsonl' -print0 | xargs -0 grep -h "must precede an" \
| python3 -c "
import sys,json
for l in sys.stdin:
try: d=json.loads(l)
except: continue
print(d.get('timestamp','?')[:19], d.get('version','?'))
" | sort
On my machine this returns zero rows for all 28 versions from 2.1.142 (2026-05-26) through 2.1.260, and the first row is 2026-09-11T09:24:56 on 2.1.266 — the first day 2.1.266 appears in my logs.
Scope check — the failures are on main-thread interactive turns, not subagents. Every affected transcript entry has userType "external" and isSidechain false; zero sidechain occurrences across 14 sessions in 3 projects.
I can't produce a minimal code example because no file or code triggers it — the malformed request is built by the client, and nothing in the user's project is involved. If it helps, I can supply the raw transcript entries around a failure, though I'd need to redact work content first.
### Claude Model
Opus
### Is this a regression?
Yes, this worked in a previous version
### Last Working Version
_No response_
### Claude Code Version
2.1.270
### Platform
Anthropic API
### Operating System
macOS
### Terminal/Shell
Terminal.app (macOS)
### Additional Information
Version reporting caveat: `claude --version` returns 2.1.270, but the desktop app pins a bundled CLI per session. My current session runs 2.1.266 (CLAUDE_CODE_EXECPATH=.../Claude/claude-code/2.1.266/claude.app/..., AI_AGENT=claude-code_2-1-266_agent), which is why failures dated 2026-09-14 still log version 2.1.266 alongside ones logging 2.1.270. Updating the CLI may not move existing sessions off the affected build.
Scope: ~48 failed turns across 14 sessions in 3 projects since 2026-09-11. All on main-thread interactive turns — every affected entry has userType "external" and isSidechain false, zero sidechain occurrences.
No hooks configured. settings.json contains only permissions, theme, skipWorkflowUsageWarning.
One lead, unconfirmed: many failures are preceded within a few transcript entries by client-generated system-subtype records — most often system/stop_hook_summary, also system/turn_duration and system/away_summary. stop_hook_summary appears despite no hooks being configured. Possibly these internal system records are serialized into the outgoing messages array without the position check the API enforces. Also frequently nearby: queue-operation (queued user messages) and attachment / file-history-snapshot records.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.