-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(claude): keep mid-conversation system messages in the timeline #4161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -150,10 +150,17 @@ function blockedSkillCallIds(messages: readonly unknown[], blocked: readonly str | |
|
|
||
| /** | ||
| * Claude Code (observed 2026-07-11, real CLI smoke) sends `role:"system"` entries in | ||
| * `messages` despite the published API having no system role. Map them to Responses | ||
| * instructions text: the native ChatGPT backend rejects system message items in | ||
| * `input` ("System messages are not allowed", verified live), so folding into | ||
| * `instructions` is the only shape that works on every route. | ||
| * `messages` despite the published API having no system role. They are emitted as | ||
| * chronological `role:"developer"` input items, which keeps the timeline intact and | ||
| * leaves `instructions` owned solely by the top-level Anthropic `system` field. | ||
| * | ||
| * The original mapping folded them into `instructions` because the native ChatGPT | ||
| * backend rejects `role:"system"` items in `input` ("System messages are not allowed", | ||
| * verified live). That constraint is real and still respected — but it only rules out | ||
| * `system`, not `developer`, which every Responses route accepts. Folding meant each | ||
| * mid-conversation reminder mutated the prompt head, invalidating the upstream KV | ||
| * prefix and rotating the Desktop `prompt_cache_key` fallback below on every turn | ||
| * (#4148). | ||
| */ | ||
| function systemMessageText(content: unknown): string { | ||
| if (typeof content === "string") return content; | ||
|
|
@@ -333,7 +340,12 @@ function translateAnthropicRequest(raw: unknown, cc: OcxClaudeCodeConfig | undef | |
| else if (msg.role === "assistant") assistantMessageToItems(msg.content, input, budget); | ||
| else if (msg.role === "system") { | ||
| const text = systemMessageText(msg.content); | ||
| if (text.length > 0) systemParts.push(text); | ||
| // Keep it where the client put it. `developer` is first-class in the Responses | ||
| // schema and survives parseRequest as a chronological message, where `system` | ||
| // would be re-hoisted back onto the system prompt and defeat the point. | ||
| if (text.length > 0) { | ||
| input.push({ type: "message", role: "developer", content: [{ type: "input_text", text }] }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For an accepted request that has in-message system entries but neither top-level Useful? React with 👍 / 👎. |
||
| } | ||
| } | ||
| else throw new AnthropicRequestError(`unsupported message role: ${String(msg.role)}`); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a
role:"system"entry occurs between an assistanttool_useand its matching usertool_result, inserting this developer item breaks their adjacency. On the Ollama-native route,buildNativeMessagestreats the developer item as a hard boundary and callsflushPending(), which throws because the following result has not been processed yet; the Anthropic and Google adapters similarly synthesize a missing result and later downgrade the real result to an orphan. This request shape worked before because the system entry was folded out of the message timeline, so defer such reminders until after the result batch or make each affected adapter preserve the pending pair across this barrier.AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.