fix(sse): hoist leading text system messages on the Claude mid-conversation-system passthrough (Anthropic 400) - #13072
Merged
diegosouzapw merged 1 commit intoSep 9, 2026
Conversation
…sation-system passthrough
On the Claude Code semantic passthrough with a 1M-context model (system + tools
present), system-role messages are deliberately kept inside messages[] and only
directive-only messages (content: [] + output_config) are relocated off
messages[0]. Anthropic also rejects a text-bearing system message at messages[0]:
messages.0: use the top-level 'system' parameter for the initial system
prompt; the directive-only form (content: [] with output_config) is
accepted at any position
That is exactly where the Output Styles injection lands
(open-sse/services/compression/outputStyles/apply.ts unshifts a system message),
so every Claude Code turn with an output style active on such a model fails
with 400.
Add hoistLeadingTextSystemMessages(): move only the leading run of text-bearing
system-role messages (everything before the first user/assistant turn) into the
top-level system parameter, and call it before relocateDirectiveOnlyMessages()
on that path. Genuine mid-conversation system turns keep their position and
cache prefix; directive-only messages in the run are left for the existing
relocation. Unit tests cover the injected-style case, the string top-level
system case, mixed directive/text runs, and the no-op case.
Repro: POST /v1/messages with Claude Code client headers (user-agent
claude-cli/..., x-app: cli), model claude-opus-5, a top-level system, one tool,
and messages[0] = {role: "system", content: "[OmniRoute Output Styles] ..."}.
Before: 400 from Anthropic. After: 200.
diegosouzapw
added a commit
to onexer88-blip/OmniRoute
that referenced
this pull request
Sep 24, 2026
…souzapw#13887 fix Restore every file except accountFallback.ts and its test to the release tip: the branch carried main-only content (diegosouzapw#13072 hoistLeadingTextSystemMessages in chatCore.ts and its test, model-catalog and usage-ordering changes) that the release does not have and that broke the merge train. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw
added a commit
that referenced
this pull request
Sep 29, 2026
Brings the 12 main-only commits into the frozen release branch so the release PR (#11442) is no longer CONFLICTING. Most are main twins of fixes the release branch already carries in a newer shape; resolved by proving the release side contains them: - #12246 temp-dir retry: release uses cleanupTempDataDir / maxRetries (kept ours). - #11719 Bun image: release is already on oven/bun 1.4.2 (kept ours). - #12086 CVE-2025-68121 .trivyignore: dropped on release by #12429 (wreq-js). - #13865 npm 11 / electron overrides: release pins are newer (kept ours); the optional transformers webpack-ignore change merged cleanly. - streamTiming clock injection: release moved to a monotonic clock (kept ours). Real main-only fix ported: #13072 hoistLeadingTextSystemMessages, added to the release's claudeSystemRole.ts (the call site in chatCore.ts merged cleanly).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
On the Claude Code semantic passthrough path (provider
claude, 1M-context model,system+toolspresent), system-role messages are kept insidemessages[]and only directive-only messages are relocated offmessages[0]. Anthropic rejects a text-bearing system message atmessages[0]as well:That is exactly where the Output Styles injection lands (
outputStyles/apply.tsunshifts a system message), so with an output style active every Claude Code turn on such a model returns 400.Fix
hoistLeadingTextSystemMessages()inopen-sse/handlers/chatCore/claudeSystemRole.ts: move only the leading run of text-bearing system-role messages (everything before the first user/assistant turn) into the top-levelsystemparameter, called beforerelocateDirectiveOnlyMessages()on that path. Genuine mid-conversation system turns keep their position and cache prefix; directive-only messages in the leading run are left to the existing relocation. An existing top-levelsystem(string or blocks) is preserved and the hoisted blocks are appended.Tests
tests/unit/claude-leading-text-system-hoist.test.ts: injected-style case, string top-level system, mixed directive/text leading run (combined with the relocation), and the no-op case. The existing directive-only and mid-conversation passthrough tests still pass.Repro
POST /v1/messageswith Claude Code client headers (user-agent: claude-cli/...,x-app: cli), modelclaude-opus-5, a top-levelsystem, one tool, andmessages[0] = {role: "system", content: "[OmniRoute Output Styles] ..."}followed by a user turn. Before: 400 from Anthropic. After: 200. Running on a self-hosted instance since 2026-09-04.🤖 Generated with Claude Code