Skip to content

fix(sse): make anthropic-compatible mid-conversation system turns a per-connection opt-in - #18341

Merged
diegosouzapw merged 27 commits into
cheaper-inference:release/v3.8.53from
MikeTuev:fix/anthropic-compatible-mid-system-per-connection
Nov 5, 2026
Merged

diegosouzapw merged 27 commits into
cheaper-inference:release/v3.8.53from
MikeTuev:fix/anthropic-compatible-mid-system-per-connection

Conversation

@MikeTuev

Copy link
Copy Markdown
Contributor

Summary

  • What was broken: fix(sse): keep mid-conversation system turns in place on anthropic-compatible relays #16236 keeps mid-conversation role: "system" turns inside messages[] for plain
    anthropic-compatible-* relays only when the global ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM flag is on
    and the client itself sends the mid-conversation-system-2026-04-07 anthropic-beta. Several Claude Code
    builds (cc_entrypoint=cli 2.1.299, claude-desktop-3p 2.1.308 on our deployment) put those turns in the body
    but do not send the beta. With the flag on, their turns are still hoisted into the top-level system.
    system then grows by one <total_tokens> line every turn and the cached history after it never matches:
    one session read 30461 → 30482 → 30503 → 30524 … from cache (exactly +21 tokens per turn) while ~110–120K was
    re-written every turn. A client on the same relay that does send the beta read 93% from cache.
  • Root cause: whether role: "system" inside messages[] is accepted is a property of the relay, not of
    the client. Gating on the client's beta silently disables the fix for clients that never negotiate it, and a
    global flag cannot distinguish relays that 400 on the shape from relays that accept it.

Fix: the decision moves to the connection.

  • New providerSpecificData.keepMidConversationSystem (strict boolean, default off), shown as a toggle in the
    edit-connection modal for plain anthropic-compatible-* only (not CC-compatible / native claude), en/ru
    strings, validated + normalized like the other boolean knobs.
  • resolveClaudeMidConversationSystemPolicy() now reads that opt-in from the connection's
    providerSpecificData (plumbed through normalizeClaudeUpstreamMessages → liftSystemRoleMessages) and no
    longer requires the client beta. The client's own beta is still forwarded when present
    (applyClientAnthropicBeta, unchanged), never invented.
  • The global ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM flag is removed: definition, i18n description, and
    the FEATURE_FLAGS.md rows / counts (111 → 110 flags, Runtime 53 → 52) in all locales.
  • Default behaviour (opt-in off) is byte-for-byte the legacy full hoist, as before.

Migration note: an operator who had the global flag on needs to enable the toggle on the relevant
connections. The flag's DB override row becomes inert.

Related Issues

Target Branch

  • Base is the active release/v3.8.x branch (the highest open release/v*), not main

Proof Of Fix (required)

  • TDD: tests/unit/claude-mid-conversation-system-compatible-passthrough.test.ts drives the real
    handleChatCore with fetch captured. New cases: beta-less client and client without any anthropic-beta
    stay in place with the beta never invented; a beta-less client across successive turns keeps system stable
    and earlier messages a prefix; strict-boolean policy; validation/normalization.
    Production files reverted to the base, test kept:
    ✖ keepMidConversationSystem: validated and normalized as a strict boolean
    ✖ opt-in on, client without the beta: turns stay in place and the beta is never invented
    ✖ opt-in on, client without any anthropic-beta header: in place, no beta header invented
    ✖ opt-in on, beta-less client: successive turns keep system stable and messages a prefix
    ✖ policy: scope and strict per-connection opt-in
    … (11 fail / 5 pass)
    
    After the fix: all pass.
  • Real-environment smoke — runner-base image, throwaway container, plain anthropic-compatible-*
    connection pointed at a capture relay, Claude Code-shaped request with mid-conversation system turns and a
    client anthropic-beta without the mid-conversation token:
    • toggle off: upstream got roles [user, assistant, user], system 3 blocks (hoisted);
    • toggle on: upstream got [user, system, assistant, user, system], system 1 block, no beta invented, 200.
  • Production evidence (read-only analysis of call logs on our deployment) is summarized above.

Scope

  • Only the files needed for the opt-in move are touched (+ flag removal in docs/i18n, the two flag-count tests)
  • No test was weakened or deleted; the "flag on + no beta → hoisted" cases were inverted to the new contract

Validation

  • Change type: routing (+ small UI/i18n, docs)
  • 44 neighbouring unit files (mid-conversation, claude passthrough, system role, anthropic headers/beta,
    feature-flags incl. doc-sync, provider-specific-data, executor-default, cc-compatible, chatcore translation):
    533/533
  • npm run typecheck:core, eslint on touched files
  • npm run check:docs-all: 81 STRICT drifts, identical on the base (translated README migration counts 210 vs 211); none from this PR

Reviewer Notes

  • Removing a feature flag changes the public flag count; I updated every count I found (docs + the two tests).
  • If a relay rejects role: "system" in messages[] it answers 400 and the operator turns the toggle off for that connection only.
  • Touches the same edit-connection modal / validation files as fix(sse): stop prefill 400s from locking models; opt-in user-last for anthropic-compatible #18332 (both add one boolean next to disableCooling); whichever lands second needs a trivial merge.

…without the client beta

cheaper-inference#16236 kept mid-conversation role:system turns in messages[] for plain
anthropic-compatible-* relays only when the global
ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM flag was on AND the client sent
the mid-conversation-system-2026-04-07 anthropic-beta. Several Claude Code
builds (cli and claude-desktop-3p) keep those turns in the body but never send
the beta, so with the flag on they were still hoisted into the top-level
system: system grew by one <total_tokens> line each turn and the cache read
stayed flat at ~30K while ~110K was re-written every turn.

Whether the shape is accepted is a property of the relay, not of the client,
so the decision moves to the connection: providerSpecificData
.keepMidConversationSystem (strict boolean, default off, toggle in the
edit-connection modal for plain anthropic-compatible-*, en/ru strings,
validated and normalized like the other knobs). The client's own beta is still
forwarded when present and never invented. The global feature flag, its
definition, i18n description and FEATURE_FLAGS.md rows are removed.
@freefrank

Copy link
Copy Markdown

Same here on 3.8.53, anthropic-compatible connection to CLIProxyAPI 8.0.13 (Claude OAuth).

  • Claude Code 2.1.312 sends the <total_tokens> system turns without mid-conversation-system-2026-04-07. It also drops that token from ANTHROPIC_BETAS (debug log: "is generally available; the header is not sent"), so there's no client-side way to opt in.
  • With only the client-beta check removed from resolveClaudeMidConversationSystemPolicy, cache reads went from stuck at ~40k to 93–99% from the second turn.
  • CLIProxyAPI accepts the in-place system turns without the beta: 200, turn 2 reads all of turn 1.

diegosouzapw and others added 5 commits November 3, 2026 00:28
# Conflicts:
#	docs/i18n/am/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ar/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/az/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/bg/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/bn/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/da/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/de/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/el/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/es/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/et/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/fa/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/fi/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/fr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ga/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/gu/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ha/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/hr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/hu/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/hy/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/id/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ig/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/it/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ka/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/km/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/kn/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/lt/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/lv/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ml/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/mr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ms/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/mt/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/my/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ne/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/nl/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/no/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/or/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/pa/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/phi/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/pt-BR/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/pt/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ro/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ru/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sl/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sv/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sw/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ta/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/te/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/th/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/uk-UA/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ur/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/uz/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/vi/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/yo/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/zh-TW/docs/reference/FEATURE_FLAGS.md
#	docs/reference/FEATURE_FLAGS.md
#	tests/unit/feature-flags-settings.test.ts
#	tests/unit/server-owned-tool-loop-flag.test.ts
…ocale

Fills providers.keepMidConversationSystemLabel and keepMidConversationSystemDescription in the 65 locales that lacked them so i18n:check-keys passes. Machine translated; the next i18n wave can polish them.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…-per-connection

Resolve the feature-flag count conflicts after the tip grew to 114 flags: the removed ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM flag brings the catalog to 113 (Runtime 54) in the English reference, the two count tests and the FEATURE_FLAGS mirrors.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
…-per-connection

Resolve FEATURE_FLAGS.md i18n mirror conflicts after the tip retouched them: take the tip mirror, drop the removed ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM row, flag count 114 -> 113, Runtime 55 -> 54.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
# Conflicts:
#	docs/i18n/am/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ar/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/az/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/bg/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/bn/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/bs/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/cs/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/da/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/de/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/el/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/es/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/et/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/fa/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/fi/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/fr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ga/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/gu/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ha/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/he/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/hi/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/hr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/hu/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/hy/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/id/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ig/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/it/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ja/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ka/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/km/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/kn/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ko/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/lt/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/lv/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ml/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/mr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ms/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/mt/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/my/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ne/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/nl/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/no/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/or/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/pa/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/phi/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/pl/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/pt-BR/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/pt/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ro/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ru/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/si/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sk/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sl/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sv/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/sw/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ta/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/te/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/th/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/tr/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/uk-UA/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/ur/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/uz/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/vi/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/yo/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/zh-CN/docs/reference/FEATURE_FLAGS.md
#	docs/i18n/zh-TW/docs/reference/FEATURE_FLAGS.md
#	docs/reference/FEATURE_FLAGS.md
#	tests/unit/feature-flags-settings.test.ts
#	tests/unit/server-owned-tool-loop-flag.test.ts
@coderabbitai

coderabbitai Bot commented Nov 4, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

# Conflicts:
#	docs/reference/FEATURE_FLAGS.md
#	src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx
#	src/shared/constants/featureFlagDefinitions.ts
#	tests/unit/feature-flags-settings.test.ts
#	tests/unit/server-owned-tool-loop-flag.test.ts
@diegosouzapw
diegosouzapw merged commit 17d7e63 into cheaper-inference:release/v3.8.53 Nov 5, 2026
3 of 8 checks passed
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.

3 participants