Skip to content

fix: accept inline system messages and merge them into system prompt - #262

Open
aceaura wants to merge 2 commits into
jwadow:mainfrom
aceaura:fix/inline-system-messages
Open

fix: accept inline system messages and merge them into system prompt#262
aceaura wants to merge 2 commits into
jwadow:mainfrom
aceaura:fix/inline-system-messages

Conversation

@aceaura

@aceaura aceaura commented Jul 27, 2026

Copy link
Copy Markdown

What

Accept role: "system" / role: "developer" messages inside the messages array and merge their text into the system prompt instead of rejecting the request.

Fixes #190, #219, #255.

Why

Claude Code and Claude Desktop place runtime context (<system-reminder> blobs, memory files, session hooks) in a message with role: "system" inside messages. AnthropicMessage.role is Literal["user", "assistant"], so Pydantic rejects the request during FastAPI validation — before any downstream normalization runs — and the client cannot complete a single turn:

422 {"loc": ["body", "messages", 1, "role"],
     "msg": "Input should be 'user' or 'assistant'",
     "input": "system"}

How

Two parts, because widening the Literal alone is not enough:

  1. models_anthropic.pyAnthropicMessage.role accepts system and developer, so the request passes validation. Unknown roles are still rejected, keeping the field constrained.

  2. converters_anthropic.py — new split_inline_system_messages() peels those messages off and appends their text to the system prompt; anthropic_to_kiro() calls it before convert_anthropic_messages().

Widening the Literal on its own would let normalize_message_roles() fold the message into a user turn, which puts client context into the conversation as if the user had typed it and triggers a synthetic assistant turn to keep roles alternating. Merging into the system prompt instead mirrors what convert_openai_messages() already does for OpenAI role: "system", so both APIs now treat inline system messages the same way. Top-level system stays first; inline parts are appended in the order the client sent them.

This matches the approach @W0n9 proposed in #190.

Tests

tests/unit/test_models_anthropic.pyTestAnthropicMessageRoles (5 tests): each accepted role validates (parametrized), unknown roles still raise, and a full request carrying a system message validates.

tests/unit/test_converters_anthropic.pyTestSplitInlineSystemMessages (7 tests): extraction of system and developer roles, multiple system messages joined in order, text extracted from content blocks (the cache_control shape Claude Code sends), empty content skipped, normal conversations unchanged, empty list. TestAnthropicToKiroInlineSystem (4 tests): inline context reaches the Kiro payload, top-level system precedes inline text, the system message does not become a history turn, and a request of only system messages raises ValueError (surfaced as a client error by routes_anthropic).

Full suite: 1708 passed.

🤖 Generated with Claude Code

aceaura and others added 2 commits July 27, 2026 08:00
Claude Desktop and Claude Code place reminder/memory blocks in a message
with role "system" inside the messages array. The Literal constraint
rejected those at the FastAPI validation layer, so the request 422'd
before reaching the converters:

  422 {"loc":["body","messages",1,"role"],
       "msg":"Input should be 'user' or 'assistant'"}

build_kiro_payload() already folds unknown roles into user via
normalize_message_roles(), so accepting them in the model is enough --
no converter change needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Accepting the role was only half the fix: normalize_message_roles() folds
an inline system message into a user turn, which puts client context into
the conversation as if the user had typed it, and inserts a synthetic
assistant turn to keep roles alternating.

Peel those messages off instead and append them to the system prompt,
mirroring convert_openai_messages(). Top-level system stays first.

Refs jwadow#190, jwadow#219, jwadow#255

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

Thanks for the PR! 🎉

Before merge, we need a one-time CLA confirmation.
It confirms that you have the right to contribute this code and allow the project to use it.

Full CLA text:
https://github.com/jwadow/kiro-gateway/blob/main/CLA.md

Please reply once with:

I have read the CLA and I accept its terms

You need to write once, all further messages from me can be ignored.

@aceaura

aceaura commented Jul 27, 2026 via email

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: 422 error when newer Claude Code client inlines role: "system" in messages array

1 participant