[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"
Preflight Checklist
- [x] I have searched existing issues 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
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:
- Install 2.1.266 or later (reproduced on 2.1.266, 2.1.268, 2.1.270).
- 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).
- 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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗