fix(cli): preserve envBlock on all user messages to fix prompt cache invalidation - #7778
fix(cli): preserve envBlock on all user messages to fix prompt cache invalidation#7778marius-kilocode wants to merge 3 commits into
Conversation
…invalidation The environmentDetails block was only injected into the *last* user message ephemerally. When a new user turn started, the previous user message lost its envBlock, changing the byte content in the middle of the conversation and breaking Anthropic's prefix cache match. Only the ~30k system prompt prefix survived, leaving 100k-180k of conversation history uncached at full price. Fix: cache each user message's envBlock in a Map keyed by message ID and inject into ALL user messages that have one, so the conversation prefix stays byte-identical across turns. Introduced by: f6da5de (move dynamic editor context from system to user msg) Worsened by: 376cffa (inject environment_details ephemerally)
| // Without this, the envBlock would only be on the *last* user message, | ||
| // causing earlier user messages to lose theirs on new turns and breaking | ||
| // the prefix match (everything after that point shifts). | ||
| const envCache = new Map<string, string>() |
There was a problem hiding this comment.
WARNING: This cache is scoped to a single loop() invocation, so it resets on every new user turn or resume.
envCache only lives for the current assistant loop. When the next user message starts a fresh SessionPrompt.loop() call, all older user IDs are missing from the map, so only the newest lastUser gets <environment_details> injected again. That means earlier user messages still lose their env block at turn boundaries, and Anthropic's longest-prefix cache miss still happens. Persist the rendered block with the message, or recompute it from each stored editorContext, instead of keeping it in a local Map.
Code Review SummaryStatus: 1 Issue Found | Recommendation: Address before merge Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
Files Reviewed (1 files)
Reviewed by gpt-5.4-20260305 · 175,944 tokens |
Remove second-precision time from the environment_details block. The model has no use for knowing it's 13:32:40 — the date and timezone are sufficient. Add tests to enforce no time-of-day leaks into environment_details.
…ries The envCache Map was local to loop(), which is called fresh for each user turn. This meant old user messages still lost their envBlocks on new turns. Move envCache to module scope keyed by sessionID:messageID, and clean up entries when a session is cancelled.
|
Is this still recent/the case? do you still want to merge? |
Summary
Fixes Anthropic prompt cache invalidation at turn boundaries that was causing $32+ of waste per long session.
The
environmentDetailsblock (containing timestamp, active file, open tabs) was only injected into the last user message ephemerally. When a new turn started, the previous user message lost its envBlock, changing the byte content in the middle of the conversation and breaking Anthropic's prefix cache match. Only the ~30k system prompt prefix survived as a cache hit, leaving 100k–180k of conversation history uncached at full input token price.Root Cause
Anthropic's prompt caching uses longest prefix matching. The cache looks for the longest byte-identical prefix between the current request and a previous cached request.
When the envBlock moved from
user_msg_1touser_msg_2at a turn boundary, the bytes atuser_msg_1's position changed (it lost its<environment_details>block). Sinceuser_msg_1sits right after the system prompt (~30k), every byte after position ~30k shifted, and the longest matching prefix was only the system prompt.Turn 1 (envBlock on user_1):
Turn 2 (envBlock moves to user_2, user_1 loses it):
Fix
Replace the single
envBlock/envUservariables with aMap<string, string>that caches each user message's envBlock by message ID. On each loop step, inject the cached envBlock into every user message that has one — not just the last. This preserves the byte-identical prefix across turns.Impact
Analyzed session
ses_2d178770dffePbbmynWytbfSwt(Claude Opus 4.6 via Kilo Gateway):Introduced by
The cache invalidation was introduced by #6225, specifically:
f6da5ded83—refactor: move dynamic editor context from system prompt to user message— moved envBlock to the last user message only376cffa3c4—fix: inject environment_details ephemerally to avoid stale accumulation across turns— made injection ephemeral so old user messages lose their envBlock when they're no longer "last"The architectural decision to move dynamic content out of the system prompt was correct (it protected the system prompt cache), but the single-last-user-message injection strategy introduced the turn-boundary cache invalidation.
Related
Testing