fix(responses): scope fallback prompt_cache_key per conversation - #2031
Merged
looplj merged 2 commits intoAug 1, 2026
Merged
Conversation
The session-ID fallback introduced for looplj#1348 works well when one session maps to one conversation, but Claude Code multiplexes many concurrent conversations (subagents) over a single session_id. All of them share one prompt_cache_key, so the upstream routes every conversation to the same cache shard and their 100k+ token prefixes evict each other. Append a deterministic conversation anchor (hash of the leading system/developer messages plus the first user message) to the session ID so each conversation gets a stable, distinct cache key. Client-provided keys are preserved untouched, and no key is emitted where none was emitted before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Greptile SummaryThis PR scopes fallback cache keys to individual conversations within a shared session. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (2): Last reviewed commit: "fix(responses): harden conversation anch..." | Re-trigger Greptile |
… non-text content Address review feedback: - Apply the hash budget per content unit instead of globally, so a large leading system/developer prefix can never starve the first user message out of the fingerprint. - Include non-text content parts (image/video/document/audio/compaction) in the fingerprint, with a length prefix to disambiguate oversized units that share their first bytes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
|
只考虑 system 和 第一个 user message 是否足够 |
Contributor
Author
|
够的。这个 key 需要在同一会话的每一轮保持不变,而请求里逐轮不变的前缀恰好只有开头的 system/developer 消息 + 第一条 user 消息 —— 再多哈希后续消息,key 会逐轮变化反而破坏缓存。两个会话只有 system + 首条 user 完全一致时才会共用 key,此时它们的真实前缀本就相同,共用分片不会比现状差。 补丁在我的生产网关运行一周后的数据(Claude Code 流量,anthropic/messages 入站):
≥50k token 的大请求平均命中从 70k 升到 98k(53.4% → 79.3%)。对照:同网关上自带 per-thread key 的 Codex 流量为 96.4%,余下差距主要来自会话首轮冷启动和 agent 重试。 |
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.
Problem
The session-ID fallback introduced in #1350 (fixes #1348) works well when one session maps to one conversation. But Claude Code multiplexes many concurrent conversations over a single
session_id: every subagent (Task/Explore agent) runs its own conversation, whilemetadata.user_idcarries the same session ID for all of them.All these conversations therefore go upstream with the same
prompt_cache_keyand the same prompt head, so the provider routes them to the same cache shard, where their 100k+-token prefixes keep evicting each other.Measured impact
Same gateway, same upstream channel (OpenAI Responses relay), same model, 3-day window:
During a Claude Code subagent fan-out (~20 concurrent conversations), nearly every request hit exactly 4,608 or 5,632 cached tokens regardless of prompt size (11k–142k): only the static system+tools prefix survives, and every conversation tail misses on every turn.
With this patch deployed on the same setup (3 parallel subagents): 81% for the whole session including unavoidable cold starts, with steady-state turns at 95–99% (e.g. 152,064 cached / 152,686 prompt). Distinct per-conversation keys were confirmed in the stored outbound bodies.
Change
When the fallback applies (client provided no
prompt_cache_key), append a deterministic conversation anchor to the session ID:Why this cannot be fixed client-side
Unlike OpenCode (tracing plugin) or Hermes (client-side patch), Claude Code has no mechanism to attach per-conversation identity:
metadata.user_idis session-scoped and shared by all subagents, and custom headers are static per process. The protocol translation layer is the only place that holds both the conversation content and the outbound field.Tests
TestOutboundTransformer_TransformRequest_UsesSharedSessionIDAsPromptCacheKeyFallbackfor the new key shape.TestOutboundTransformer_TransformRequest_PromptCacheKeyScopedPerConversation: same conversation across turns → same key; sibling conversations → distinct keys; client-provided key untouched.cd llm && go build ./... && go test ./transformer/openai/responses/ && go vet ./transformer/openai/responses/all pass.中文摘要
Claude Code 的 subagent 会在同一个 session 里并发跑多个会话。#1350 的兜底让它们共用同一个
prompt_cache_key,上游把所有会话路由到同一个缓存分片,互相驱逐——实测同一上游同一模型:Codex(线程级 key)96.8% vs Claude Code(session 级 key)32.5%,subagent 并发期间每请求不论 prompt 多大都恒定只命中 ~5k token 的静态前缀。本 PR 在兜底 key 上追加"会话锚点"(开头 system/developer 消息 + 首条 user 消息的 SHA-256 前 16 位十六进制):同会话逐轮 key 稳定,不同会话自然分流。客户端显式传 key 时不受影响,也不会给原本收不到该字段的后端新增字段(#1429 中讨论的第三方后端回归风险不适用)。补丁部署后同场景实测:整体 81%(含冷启动),稳态轮次 95–99%。
与 OpenCode/Hermes 不同,Claude Code 没有任何客户端机制能按会话(subagent)注入标识——
metadata.user_id是 session 级的、自定义头是进程级静态的——因此只能在协议转换层修。🤖 Generated with Claude Code