CacheLint paper — Prompt-cache preconditions — What does it mean?
The news. On October 26, 2026, researchers from Wake Forest, Griffith, NTU, UNSW and SUSTech posted a study of prompt-caching bugs in LLM agents. They labeled 120 caching pull requests from 10 popular open-source agent systems, built CacheLint, and ran it on 571 agent projects, where it found 32 more mis-integrations. Read the paper →
Picture the print shop. Your agent sends the same long booklet on every turn: instructions, tool descriptions, then the conversation so far. The shop only reuses a shelved copy if your booklet matches it from page 1, character by character. So a clock reading stamped on page 1 turns every later page into a mismatch, and the shop reprints the whole booklet at full price. The model still answers correctly, which is why nobody notices.
The paper sorts the real fixes by who reads or writes the cache wrongly. 81% of the 120 defects are on the request side (97 cases): the app builds the request so the cache cannot hit. The other 19% are on the read side (23 cases): the cache works, but the app misreads the receipt, for example by hardcoding cache-creation tokens to zero. On the request side, two families dominate. In provider compatibility, an app turns caching on by model name, so a renamed model silently loses it; this is what happened in the cline project when a Gemini model ID changed. In prefix stability, a volatile value such as a timestamp or a random call ID sits inside the cached span.
CacheLint turns this into a small formal model. Every bug is a pair: a call site, plus a condition that must hold at that site and is not guarded. The site is a line that attaches a marker (like cache_control) or reads a usage field (like cached_tokens). The condition belongs to one of three families: prefix stability, provider capability, or accounting safety. CodeQL finds the sites. Then the check depends on the family: capability conditions are checked against a dated provider table, not by a language model, because the answer lives outside your code and can change after release (the paper notes that when Bedrock added a one-hour TTL, previously correct code silently became defective). Stability and accounting conditions are checked by an LLM that reads the code around the site and decides whether a guard exists.
| Root cause | Real example from the study | What the owner sees | Fix pattern |
|---|---|---|---|
| Model-name gating | cline: only IDs starting with claude got cache_control, so a renamed Gemini model lost caching | Bill rises, no error | Ask a capability lookup, not a hardcoded name |
| Volatile value in the prefix | goose: a live timestamp in the system prompt | Cache never hits | Move the changing value out of the cached span |
| Prefix reassembly | hermes-agent: the system prompt rebuilt every turn, reported as roughly 75% more cost | Higher cost | Rebuild the prefix in one fixed order |
| Nondeterministic serialization | openclaw: map order differs per instance | Identical content, different bytes, miss | Sort keys so the bytes are canonical |
| Cross-turn mutation | openclaw: pruning old images rewrote the already-cached history | Cache rebuilt instead of read | Do not edit cached history mid-session |
| Usage miscount | cline: cache-creation tokens hardcoded to zero | Meter says caching is off while it works | Parse and sum the provider's fields correctly |
Here is why one stray timestamp matters, with illustrative numbers. Take an agent task of 30 model calls, each carrying a 20,000-token stable prefix, and say a cached token costs one tenth of a normal input token (the paper cites roughly one-tenth). With the prefix broken on every call, the task pays for 30 × 20,000 = 600,000 full-price tokens. With a stable prefix, the first call writes the cache at full price (20,000) and the other 29 calls read it at one tenth: 29 × 20,000 × 0.1 = 58,000, for a total of 78,000 token-equivalents. That is about 7.7× cheaper for the same answers (illustrative). Real cache-write surcharges and TTL expiry would move the numbers, but the shape holds: the loss is large and invisible in the output.
Two limits keep this honest. First, CacheLint is a linter, not a proof: it reached 0.71 precision and 0.78 recall on the 120 labeled cases, and 0.86 precision on the 32 new findings across 571 projects, with 21 developer confirmations and 18 upstream fixes, including one in Anthropic's official SDK. Second, two root causes sit outside its grammar: cross-turn mutation and prefix reassembly compare prefixes across different calls, and one program state cannot show that. Those still need a runtime check, such as watching the cache-hit rate in your traces.
Goes deeper in: Agent Engineering → Cost & Latency Engineering → Prompt Caching
Related explainers
- Cache-preserving mid-task system messages — a provider-side feature that adds a message without breaking the cached prefix.
- Prefix-cache state and agent irreproducibility — what else a cache can change besides your bill.