Channel messages are merged into the previous user message and re-rendered on the next request, losing the prompt cache on non-Anthropic backends

Status Open
Reported on v2.1.268
Maintainer reply None cached
Activity 0 comments · opened Sep 11, 2026

Environment: Claude Code 2.1.268 (Linux/WSL2), Channels (experimental) via the Atrium plugin (inject directly), ANTHROPIC_BASE_URL → CLI Proxy API → OpenAI Responses (gpt-5.6-sol / gpt-6-astra). Also observed on 2.1.26x sessions routed to gpt-6-astra.

What I observe. With a metadata-only diff of consecutive upstream requests (per-item hashes and byte lengths, no content), a session receiving channel messages produces requests that are not append-only: the previous user message is re-rendered (1,516 B → 2,552 B when a channel message is delivered, then split into 364/436/317 B + a new item on the next request), and reasoning/thinking items of the affected turns are inserted or removed before an earlier assistant message. Example windows from the trace: assistant:88 | user:1516 → assistant:88 | user:2552; reasoning:1835 | reasoning:1535 | reasoning:1515 | assistant:86 → reasoning:1835 | assistant:86 | user:….

It happens whether the channel message arrives during an open turn or while the session is idle (tested with a channel-side hold that delivers only after the Stop hook: same rewrites).

Why it matters. On Anthropic the change costs a small cache write (cache_control breakpoints + lookback). On OpenAI's GPT-5.6+ prefix cache, any change before the end of the prompt invalidates everything after it, and explicit breakpoints are rejected by the Codex/ChatGPT-OAuth backend. Measured: on ~300K-token sessions, 8–15 % of requests re-billed the whole context (e.g. 46 requests × ~250K uncached tokens in one day); on a fresh session, 14–21 rewrites over 20–30 requests. Related reports: #76606 (hook system-reminder transformed later), #78660 (task_reminder attached to an old human message), #64901, #80604, #48734.

Expected. A delivered channel message (and hook/system-reminder attachments) should be appended as a new user item, and already-sent items should stay byte-stable across requests, so that append-only prefix caches keep working.

Repro. Any session with a channel plugin: send a message via the channel while the session is idle, then another one; compare the two API request bodies item by item (or watch cache_read_input_tokens drop when routed to a prefix-cache backend). A full dossier (traces, journal of a channel-side hold, before/after runs) is available on request.

Am I missing a setting that changes how channel messages are injected? If this is by design, is there a supported delivery mode that does not rewrite earlier items?

View original on GitHub ↗