Skip to content

fix(core): Anthropic 4.6+ assistant-prefill 400 + thinking.display silently defaults to 'omitted' #8039

Description

@netbrah

Description

Two related, verified bugs affecting every Claude Opus/Sonnet 4.6+ model
(and every 5.x family — Fable 5, Mythos 5, Sonnet 5, etc.) on the
Anthropic wire:

Bug 1: assistant-turn "prefill" 400s with no mitigation

When Gemini-format history ends on a model turn with no follow-up
(e.g. context trimming drops the next user turn, or a subagent
transcript is replayed mid-turn), the Anthropic converter forwards a
request whose last message has role: 'assistant'. Anthropic rejects
this outright on every 4.6+ model:

This model does not support assistant message prefill. The
conversation must end with a user message.

Per Anthropic's own migration documentation, this is a model-generation
behavior change (introduced at 4.6, applies through every subsequent
family), not a backend-specific quirk — confirmed live below across
both the affected and unaffected model boundary.

Bug 2: thinking.display silently defaults to omitted on newer models

Anthropic's adaptive-thinking display field controls whether thinking
content is returned as readable text (summarized) or redacted
(omitted, with only a continuity signature). The default value
changed between model generations: Sonnet 4.6 defaults to
summarized, but Opus 4.7+ and every 5.x family (including Sonnet 5)
silently default to omitted. qwen-code never sets display
explicitly, so any caller surfacing reasoning to users sees thinking
blocks stream back with empty text on newer models — no error, just
a long pause before output with nothing shown, where the same code
worked fine on Sonnet 4.6 or Opus 4.6.

Reproduction

Bug 1 — verified live against claude-sonnet-5, claude-opus-4.6,
claude-opus-4.7, claude-opus-4.8, and claude-sonnet-4.6 via the
Anthropic Messages API (all 400 identically):

curl -s -X POST https://api.anthropic.com/v1/messages \
  -H content-type:application/json \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model":"claude-sonnet-5",
    "max_tokens":16,
    "messages":[
      {"role":"user","content":"hi"},
      {"role":"assistant","content":"Sure, here is"}
    ]
  }'
{"type":"error","error":{"type":"invalid_request_error","message":"This model does not support assistant message prefill. The conversation must end with a user message."}}

The same request against claude-haiku-4.5 (a genuinely pre-4.6-generation
model — confirmed not a proxy alias to a 4.6+ model) succeeds normally,
confirming this is a real model-generation cutoff, not a universal
Anthropic restriction:

{"content":[{"type":"text","text":"\"Hi!\"\n\nIs there anything else..."}],...}

Bug 2 — verified live against claude-sonnet-5 with a task that
warrants visible reasoning: without display set, thinking blocks
return with empty thinking text; with display: 'summarized' set
explicitly, the same request returns full, non-empty reasoning text
(confirmed by direct comparison of both request variants against the
live API).

Expected Behavior

  1. A conversation that would end on an assistant message should be
    made safe for 4.6+ models before it reaches the wire — either by
    dropping a genuinely empty trailing assistant message, or (when it
    carries real content) appending a synthetic user turn so nothing
    the model already said is discarded.
  2. thinking.display should be set explicitly to 'summarized' when
    emitting the adaptive thinking shape, so reasoning text is visible
    regardless of which model generation's silent default applies.

Suggested Fix

  • Add a stripTrailingAssistantPrefill converter option, gated on the
    existing modelSupportsAdaptiveThinking() check (the same 4.6+
    cutoff already used for the adaptive-thinking shape) — this reuses
    existing, already-correct version-detection infrastructure rather
    than introducing a new one.
  • Set display: 'summarized' unconditionally on the { type: 'adaptive' } thinking shape returned by buildThinkingConfig(). This is a
    no-op on models whose default is already summarized and required
    on models whose default silently changed to omitted.

Both are minimal, additive changes localized to
anthropicContentGenerator/converter.ts and
anthropicContentGenerator/anthropicContentGenerator.ts, following the
same options-object pattern already used for the file's other
converter passes (dropUnsignedAssistantThinking,
injectThinkingOnToolUseTurns, etc.).

Happy to open a PR for this — will link it here once ready.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions