[BUG] 2.1.154 appears to send messages[].role="system" to Anthropic-compatible providers, causing schema parse failures

Status Fixed / completed
Reported on v2.1.154
Maintainer reply None cached
Activity 3 comments · opened May 28, 2026 · closed Jun 5, 2026

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?

Claude Code 2.1.154 sends a system role message inside the messages[] array when using an Anthropic-compatible third-party endpoint via ANTHROPIC_BASE_URL.

This breaks providers that implement the standard Anthropic Messages API schema strictly, where messages[].role only accepts user or assistant, and system content is expected in the top-level system field.

This happens even with:

  • A clean CLAUDE_CONFIG_DIR
  • An empty working directory
  • claude -p "hi"
  • No project-specific files involved

The raw request captured via OTEL_LOG_RAW_API_BODIES shows:

{
  "has_top_level_system": true,
  "top_level_system_type": "array",
  "message_roles": ["user", "system"],
  "system_messages_in_messages": [
    {
      "index": 1,
      "content_preview": "The following skills are available for use with the Skill tool: ..."
    }
  ]
}
````

Earlier captures also showed plugin / SessionStart hook additional context serialized as:

```json
{
  "role": "system",
  "content": "SessionStart hook additional context: ..."
}

inside messages[].

The provider rejects this before model routing, so it affects multiple models.

What Should Happen?

When using ANTHROPIC_BASE_URL with an Anthropic-compatible provider, Claude Code should avoid sending messages[].role = "system" unless this format is known to be supported.

Expected behavior:

  1. Keep all system/context/skill/session-start content in the top-level system field, or
  2. Provide a compatibility flag that serializes system reminders as user-side <system-reminder> text, or
  3. Feature-detect provider support before sending mid-conversation system messages.

At minimum, there should be a compatibility mode for providers that implement the documented Messages API role schema strictly

Error Messages/Logs

API Error: 400 Failed to deserialize the JSON body into the target type:
messages[1].role: unknown variant `system`, expected `user` or `assistant`


Raw request body summary captured with `OTEL_LOG_RAW_API_BODIES=file:<dir>`:


{
  "model": "deepseek-v4-pro",
  "has_top_level_system": true,
  "top_level_system_type": "array",
  "message_roles": [
    "user",
    "system"
  ],
  "system_messages_in_messages": [
    {
      "index": 1,
      "content_preview": "The following skills are available for use with the Skill tool:\n\n- context7-mcp: ..."
    }
  ]
}


The same error also occurs with another model on the same provider, so this appears to fail during request schema parsing before model execution.

Steps to Reproduce

  1. Use Claude Code 2.1.154 on Windows native.
  1. Configure a third-party Anthropic-compatible endpoint:
$env:ANTHROPIC_BASE_URL = "<third-party-anthropic-compatible-base-url>"
$env:ANTHROPIC_AUTH_TOKEN = "<redacted>"
$env:ANTHROPIC_MODEL = "deepseek-v4-pro"
$env:ANTHROPIC_DEFAULT_SONNET_MODEL = "deepseek-v4-pro"
$env:ANTHROPIC_DEFAULT_OPUS_MODEL = "deepseek-v4-pro"
$env:ANTHROPIC_DEFAULT_HAIKU_MODEL = "deepseek-v4-pro"
  1. Reproduce with a clean config directory and empty workdir:
$clean = Join-Path $env:TEMP ("cc-clean-" + [guid]::NewGuid().ToString())
New-Item -ItemType Directory $clean | Out-Null

$work = Join-Path $env:TEMP ("cc-work-" + [guid]::NewGuid().ToString())
New-Item -ItemType Directory $work | Out-Null
cd $work

$env:CLAUDE_CONFIG_DIR = $clean

claude -p "hi"
  1. Actual result:
API Error: 400 Failed to deserialize the JSON body into the target type:
messages[1].role: unknown variant `system`, expected `user` or `assistant`
  1. Capture the raw request body:
$dump = "$env:TEMP\cc-api-dump"
Remove-Item $dump -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory $dump | Out-Null

$env:CLAUDE_CODE_ENABLE_TELEMETRY = "1"
$env:OTEL_LOGS_EXPORTER = "console"
$env:OTEL_METRICS_EXPORTER = "none"
$env:OTEL_LOG_RAW_API_BODIES = "file:$dump"
$env:OTEL_LOGS_EXPORT_INTERVAL = "1000"

claude -p "hi"
  1. Inspect the request roles:
$req = Get-ChildItem $dump -Recurse -Filter "*.request.json" |
  Sort-Object LastWriteTime |
  Select-Object -Last 1

Get-Content $req.FullName -Raw | jq '{
  model,
  has_top_level_system: has("system"),
  top_level_system_type: (.system | type),
  message_roles: [.messages[].role],
  system_messages_in_messages: [
    .messages
    | to_entries[]
    | select(.value.role == "system")
    | {
        index: .key,
        content_preview: ((.value.content | tostring)[0:1200])
      }
  ]
}'
  1. Observed output includes:
{
  "message_roles": ["user", "system"],
  "system_messages_in_messages": [
    {
      "index": 1,
      "content_preview": "The following skills are available for use with the Skill tool: ..."
    }
  ]
}

Claude Model

Other

Is this a regression?

Yes, this worked in a previous version

Last Working Version

Unknown exact last working version. This started after updating to 2.1.154. 2.1.154 was published at 2026-05-28T15:40:50.400Z. The first observed failure occurred shortly afterward. Likely worked on 2.1.153 or earlier, but I have not fully bisected yet.

Claude Code Version

2.1.154 (Claude Code)

Platform

Other

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

A manual request to the same provider succeeds when using the standard Anthropic Messages shape:

{
  "system": "You are a helpful assistant.",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "hi"
        }
      ]
    }
  ]
}

So the endpoint, API key, model, and billing plan are working.

The failure only appears when Claude Code sends a request containing messages[].role = "system".

This also occurred with SessionStart hook additional context from plugins. After disabling those plugins, another raw dump still showed messages[1].role = "system" containing the built-in skills list:

The following skills are available for use with the Skill tool:
...

This suggests the issue is not limited to one plugin. It appears to be how Claude Code 2.1.154 serializes session context / skill context / hook additional context into the outgoing Messages API request.

This effectively breaks Claude Code for third-party Anthropic-compatible providers that validate messages[].role according to the traditional Messages API schema.

View original on GitHub ↗

3 Comments

Zhenyi-Wang · 3 months ago

Watching this issue. Same regression with DeepSeek Anthropic-compatible endpoint — subscribed for fix updates.

JiangHe12 · 3 months ago

Confirming this still reproduces on v2.1.156 (latest as of 2026-05-29), Windows.

Environment:

  • Claude Code: v2.1.156
  • OS: Windows 11
  • Provider: Xiaomi MiMo-2.5-Pro (Anthropic-compatible endpoint via ANTHROPIC_BASE_URL)

Behavior:
Every prompt immediately fails with:

API Error: 400 messages[1].role must be either 'user' or 'assistant', but got 'system'

The v2.1.156 release notes only mention a fix for Opus 4.8 thinking block mutation.
The system-role-in-messages regression introduced in v2.1.154 is NOT fixed in v2.1.156.

Current workaround: downgrade to v2.1.153.

npm install -g @anthropic-ai/claude-code@2.1.153
github-actions[bot] · 22 days ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.