You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In yurukusa's #63147 13A–G taxonomy, sub-pattern 13F is a v2.1.154-specific regression: after /compact, /clear, or a model switch, CC rebuilds the messages array and places the system role at messages[0] instead of using the top-level system field. The API rejects this with a 400 independent of the empty-text thinking-block check — and many sessions hit 13F on a casual /compact (which happens automatically under context pressure), so the wedge presents as "session just died with no clear trigger."
13F is the only sub-pattern in yurukusa's matrix with no available workaround other than version-pinning to ≤ 2.1.153. Our v3.8.0 extensions don't touch the system field or messages[0] shape (confirmed: grep "system\\|messages\\[0\\]" proxy/extensions/thinking-block-sanitize.mjs returns nothing). This is real product white space we can occupy.
Direction
New extension proxy/extensions/system-shape-normalize.mjs that:
Detects the bad shape on the request path: Array.isArray(body.messages) && body.messages[0]?.role === "system".
Repairs by lifting that message's text content into the top-level body.system (concatenating with any existing top-level system if both are present — order: lifted-first vs existing-first is a directive call), then removing the messages[0] entry and re-indexing.
Counts only — emits system_role_lifted: true|false and a one-time stderr notice per session when it fires, so operators know they're running into 13F.
Design considerations
Order in the request-mutator chain. Must run before any other extension that scans messages and assumes the schema is valid (likely order 540, before thinking-block-sanitize at 550).
String vs array content. If messages[0] content is a list of blocks (not a string), the lift needs to extract text blocks only; non-text blocks in a "system" message would be an unrecoverable shape we should refuse to mutate (log + pass through).
Cache safety. The lift changes the request body shape; whether it changes the cacheable prefix in a way that defeats the prompt cache is a directive question. May want to make it opt-in initially (CACHE_FIX_SYSTEM_NORMALIZE=on) and validate the cache implications before flipping on by default.
Telemetry. Per-session: system_role_lifted_total counter. Useful for measuring how often 13F actually fires in production once enabled.
Why P1
Closes the matrix gap: this turns yurukusa's "only known full fix is stay on 2.1.152 + Opus 4.7 + DISABLE_INTERLEAVED_THINKING=1" into "or run cache-fix v3.9 with CACHE_FIX_SYSTEM_NORMALIZE=on."
It's a request-body mutator on a different surface from thinking-sanitize — clean separation of concerns.
Anthropic will eventually fix 13F upstream (it's a clear regression), but in the interim every operator hitting it has no path forward today.
The 13F shape may have variants we haven't seen yet. Opt-in default until we have repro coverage from ≥ 2 distinct community reports.
If the system lift changes cache-prefix behavior negatively, this could increase cache misses for some workloads. Need to verify against the cache-fix integration test harness.
Acceptance
Extension lifts messages[0] (role=system) into top-level body.system when present.
Active tool_use/tool_result pairing unaffected.
New per-session telemetry fields: system_role_lifted_total (counter) + one-time stderr notice on first lift.
Tests cover: (a) the canonical 13F shape from the #63396 repro, (b) the no-op case (already-correct request), (c) mixed-content messages[0] refuse-to-mutate, (d) idempotency on repeated requests.
Opt-in via env var; default off until cache-impact verified.
Background
In yurukusa's #63147 13A–G taxonomy, sub-pattern 13F is a v2.1.154-specific regression: after
/compact,/clear, or a model switch, CC rebuilds themessagesarray and places thesystemrole atmessages[0]instead of using the top-levelsystemfield. The API rejects this with a 400 independent of the empty-text thinking-block check — and many sessions hit 13F on a casual/compact(which happens automatically under context pressure), so the wedge presents as "session just died with no clear trigger."13F is the only sub-pattern in yurukusa's matrix with no available workaround other than version-pinning to ≤ 2.1.153. Our v3.8.0 extensions don't touch the
systemfield ormessages[0]shape (confirmed:grep "system\\|messages\\[0\\]" proxy/extensions/thinking-block-sanitize.mjsreturns nothing). This is real product white space we can occupy.Direction
New extension
proxy/extensions/system-shape-normalize.mjsthat:Array.isArray(body.messages) && body.messages[0]?.role === "system".body.system(concatenating with any existing top-levelsystemif both are present — order: lifted-first vs existing-first is a directive call), then removing themessages[0]entry and re-indexing.system_role_lifted: true|falseand a one-time stderr notice per session when it fires, so operators know they're running into 13F.Design considerations
messagesand assumes the schema is valid (likely order 540, beforethinking-block-sanitizeat 550).messages[0]content is a list of blocks (not a string), the lift needs to extract text blocks only; non-text blocks in a "system" message would be an unrecoverable shape we should refuse to mutate (log + pass through).CACHE_FIX_SYSTEM_NORMALIZE=on) and validate the cache implications before flipping on by default.system_role_lifted_totalcounter. Useful for measuring how often 13F actually fires in production once enabled.Why P1
CACHE_FIX_SYSTEM_NORMALIZE=on."Caveats / risks
systemlift changes cache-prefix behavior negatively, this could increase cache misses for some workloads. Need to verify against the cache-fix integration test harness.Acceptance
messages[0](role=system) into top-levelbody.systemwhen present.system_role_lifted_total(counter) + one-time stderr notice on first lift.messages[0]refuse-to-mutate, (d) idempotency on repeated requests.References
— AI Team Lead