Cache Clock · GitHub Copilot CLI and VS Code

One idle gap, one 25× price.

Copilot bills tokens in AI credits. When the prompt cache expires, the next message rewrites your whole conversation at the cache-write price. Cache Clock shows the clock, prices the send, and asks before a costly cold send goes out.

Tested on Copilot CLI 1.0.95 · numbers measured 11 October 2026 · 1 AI credit = $0.01

FilmThe whole idea in 17 seconds, silent. Made with make-motion.
Fig. 1The same 37.2k-token context costs $0.003 to read and $0.08 to rewrite. Real capture, Claude Sonnet 5.5. Bars share one scale from zero.
1 · The problem

A cache write costs far more than a cache read

On Claude and newer GPT models in Copilot, writing a prompt to cache is priced well above reading it back. The cache also expires. Step away longer than the TTL (time to live) and the next message, even “give me a recap”, pays the write price for everything.

Fig. 2Write price relative to read price, per token. One scale for both models.
cache warm · TTL 300 s cold 0 s · last call 300 s · cacheExpiresAt 320 s · resend: read 0, wrote 44,388 20 s after expiry
scroll sideways to see the whole timeline →
Fig. 3The cache expired exactly when Copilot said it would. The resend 20 s later read nothing and rewrote the context.
context
37.2k tokens
the estimate
cold send
$0.08
rewrite at write price
warm send
$0.003
read at read price
resend, cache read
0tokens
cache written: 44,388

The estimate (37.2k) is the last call’s input plus its answer. The real resend also carried your new message and what Copilot attaches, so it wrote 44,388.

2 · The clock

Copilot reports the expiry, so nothing is guessed

Every main-agent model call emits an assistant.usage event. It carries the cache numbers and the prices. Cache Clock keeps the last call for each model.

◆ event

assistant.usage

  • cacheReadTokens
  • cacheWriteTokens
  • cacheExpiresAt
  • cacheTtlSeconds
  • per-token prices
  • model
keeps last
call per
model →
◆ clock

One entry per model

expiry · TTL · context size · read & write price. Answers: warm, cold, or unknown?

prices
the next
send →
◆ output

Cold vs warm cost

context × write price against context × read price. The difference is the “extra”.

Fig. 4The TTL, the expiry and the prices all come from Copilot. Sub-agent and background calls are ignored: they keep their own caches.
/cache-clock — the card, from a real resumed session
cache-clock · Claude Sonnet 5.5
  cache      warm, 1m17s left (ttl 5m00s)
  context    37.2k tokens
  next send  warm $0.003 · cold $0.08 (25× per token)
  session    5 model calls · 0 cold after idle · 0 prefix breaks · $0 extra paid
  guard      refuse once above $0.10 · heads-up 5m before expiry

Prices come from the payload: nano-AI-credits per batch of tokens, and 1 credit is $0.01. Prices already include discounts. Through auto, Sonnet’s write was $2.25/M (list $2.50).

3 · The guard

A costly cold send asks first

The hook onUserPromptSubmitted runs on every message you send. It checks four things, in this order. Only the last path can stop the message, and only if you say so.

  1. Prompt submittedonUserPromptSubmitted checks the last model’s clock
  2. Starts with /name or !?Slash commands and shell escapes never call the model with your conversation.no ↓
    yes →● ALLOW · skipped

    Never guarded.

  3. Guard off, or cache not cold?Warm, or unknown (no data yet, or right after a compaction).cold ↓
    yes →● ALLOW · silent

    Compaction exemption: the context was rewritten anyway, so the next send is not an idle tax.

  4. Extra cost ≥ threshold?Default $0.10 (/cache-clock min). Extra = context × (write − read).yes ↓
    no →● ALLOW · silent

    Small rewrites pass quietly. At the default, the $0.08 capture would pass.

  5. Mode is warn, a turn is running, or this cold episode was already asked about?Episode = one model’s cache that expired at one instant. Refusals are remembered per session in refused.json for a week, across processes.none ↓
    yes →▲ SEND with a price note

    While a turn runs, aborting would cancel that turn, so you only get the note. Your resend lands here.

  6. Ask in the chatsession.ui.select with the price: Send anyway or Don't send. Don't send, dismissed, or no answer in 5 minutes: session.abort() without awaiting it (awaiting inside the hook deadlocks). No model call, 0 AI credits.
    ■ ASK · once per cold cache

    Where Copilot cannot ask (copilot -p), it refuses once instead: send the same message again to pay, or /clear and start fresh.

Fig. 5Four checks send the message through. Only a costly cold send, not yet asked about and not mid-turn, gets the question, and it goes out if you choose Send anyway.
The question (checked live in Copilot CLI and VS Code, 12 October 2026)
cache-clock: the Claude Sonnet 5.5 prompt cache went cold 2s ago. Sending this rewrites
about 37.2k tokens at $2.25/M = $0.08 (a warm send would read them for $0.003).
  [ Send anyway ]  [ Don't send ]
The refuse-once fallback, where Copilot cannot ask — a real capture (Copilot CLI 1.0.95, 11 October 2026, taken with /cache-clock min 0.01)
! cache-clock: the Claude Sonnet 5.5 prompt cache went cold 2s ago. Sending this rewrites
  about 37.2k tokens at $2.25/M = $0.08 (a warm send would read them for $0.003).
  Send it again to pay it, or /clear and start fresh.
The resend’s note — template, one line
cache-clock: sending cold, rewriting about <tokens> tokens for <cold $> (warm: <warm $>).

Switch modes with /cache-clock guard refuse (default: ask, or refuse once), warn (send and print the price) or off. /cache-clock help explains it all in the chat.

4 · The heads-up

A warning one minute before a Claude cache expires

One ephemeral line appears before expiry. It fires only when a cold send’s extra cost (cold minus warm) is at least the threshold and no turn is running. The lead time is the smaller of your setting (5 min) and a fifth of the TTL. A 5-minute lead on a 5-minute cache would fire right after every call.

Fig. 6Each bar is one full TTL. The amber end is the heads-up window: min(5 min, TTL ÷ 5) = 1 min for Claude, 5 min for Luna.

Change it with /cache-clock headsup 5, or headsup off. It keeps running under guard off until you turn it off.

5 · Resume

A resumed session reads its clock from the log

assistant.usage is not replayed when you resume, and getEvents() omits usage records. So the extension reads the session’s own log, which keeps every record with its prices.

◆ file

events.jsonl

~/.copilot/session-state/<id>/
one line per event

replays
session.usage_record
→
◆ clock

Seeded at start

Main-agent calls only. A session.compaction_complete line marks the clock unknown.

then live
events
→
◆ guard

Works on call one

Even in copilot --resume … -p.

Fig. 7The log is the source of truth after a restart. The refusal memory lives next to it in refused.json.
6 · The report

See what cold sends already cost you

The extension only sees sessions it was loaded in. The report reads every session Copilot has persisted, CLI and VS Code alike. It sends nothing anywhere. It needs Node 18+.

Run it
node ~/.copilot/extensions/cache-clock/report.mjs              # last 30 days
node ~/.copilot/extensions/cache-clock/report.mjs --since 7d   # or 12h, all; --json for raw numbers
Sample output — the test machine after the README’s experiments
model                   calls  cold after idle  prefix breaks  rewritten  extra paid  total spent  idle before cold sends
Claude Sonnet 5.5           5                1              0      37.1k       $0.08        $0.16  5m
GPT-6 Luna                201                1              2      44.9k      $0.005        $0.14  31m

extra paid for cold rewrites: $0.08 of $0.29 spent · about $0.46 per week at this rate
Fig. 8On this machine, $0.08 of $0.29 went to cold rewrites. Bars start at zero on one scale.
■ cold after idle

The call came after the reported expiry. The fix is behavior: send sooner, or accept the price.

▲ prefix break

A cold call inside the TTL. Something changed the prompt prefix (tools, instructions, model routing), or the provider served it cold early.

“Extra paid” is what each cold call cost above a warm read of the same tokens. VS Code sessions persist only totals, not per-call records; the report counts them separately. In VS Code's Copilot chat the extension loads once "chat.canvases.enabled": true is set (measured 12 October 2026); the card and the cold-send question appear as question cards, and the heads-up reaches only VS Code's log.

7 · Measured, and the limits

Claude held to the second. Luna went cold early

Copilot’s reported expiry was tested on both models on 11 October 2026.

Model (via Copilot)Reported TTLWrite / readEvidence
Claude Sonnet 5.5300 s25×Sent 20 s after cacheExpiresAt: read 0, wrote 44,388
GPT-6 Luna1800 s12.5×Every persisted call (201 on this machine) reports cacheTtlSeconds: 1800; gap test below

Gap test, GPT-6 Luna

Two sessions were warmed at 14:55Z, then resumed with copilot --resume <id> -p, guard threshold at $0.

reported warm · 30 min 30 min · reported expiry 29 min · allowed (clock said warm) · read 0, wrote 20,771 31 min · refused, 0 credits
scroll sideways to see the whole timeline →
Fig. 9The 29-minute send was already fully cold, one minute before Copilot’s reported expiry. The reported expiry is an upper bound on Luna, not a guarantee.
Resumedvs reported expiryCache readCache writtenWhat Cache Clock did
after 29 min1 min before020,771allowed it (clock said warm)
after 31 min1 min after(refused, no model call)refused, 0 AI credits
resend, new process1 min after10,15710,653let it through with the cold note

The refused-then-resent pair also shows the refusal memory working across processes. Separately, a brand-new session’s first call read 10,157 tokens from cache: sessions share the cached system-prompt prefix, while the conversation itself is per session.

Limits

▲ early eviction

The guard trusts cacheExpiresAt. When a provider serves the cache cold sooner (Luna at 29 of 30 min, once), the send is billed cold and the guard stays quiet. The report counts it as a prefix break.

▲ approximate price

The estimate is the last call’s input plus its answer. The real resend adds your message and attachments (44.4k actual vs 37.2k estimated). Copilot’s shared system prefix (~10k tokens) often stays cached, which pulls the other way.

▲ no keep-warm yet

cache-tax pings to hold the cache. An extension can only ping with session.send, which adds a user and an assistant turn each time. Run the report to see if pings would pay off.

◆ other limits

With auto, the next prompt may route to another model; the guard prices the last model used. In -p mode, /cache-clock needs the interactive CLI. Claude was measured through auto only; each boundary result is a single run.