Skip to content

feat(widgets): add prompt cache miss widgets - #770

Merged
sirmalloc merged 4 commits into
sirmalloc:mainfrom
eric-engberg:feat/prompt-cache-widgets
Oct 30, 2026
Merged

sirmalloc merged 4 commits into
sirmalloc:mainfrom
eric-engberg:feat/prompt-cache-widgets

Conversation

@eric-engberg

Copy link
Copy Markdown
Contributor

What

Four new Cache widgets show the prompt cache statistics Claude Code reports for the main conversation:

Widget Shows
Cache Misses misses out of the session's requests: Misses: 2/14
Last Cache Miss how long ago the last miss was, and its likely cause: Last miss: 3m ago, tools changed (+2)
Cache Miss Tokens tokens the misses wrote back to the cache: Miss recache: 310.2k
Cache If Cold what the next request re-caches if the cache has gone cold by then: If cold: 45.0k

Why

A cache miss re-sends content the prompt cache already held, so the whole prefix is written to the cache again. That's what makes a session's usage jump with nothing else changing: an MCP server connecting (the tool list changes), the system prompt changing, or coming back after the cache's 5-minute lifetime ran out. Until now the only place to see it was /usage.

Since 2.1.251, Claude Code sends these statistics to the status line command as prompt_cache, the same figures /usage shows on its "Prompt cache (main)" line. Since 2.1.260 it also reports the likely cause of the last miss. From the status line docs:

"prompt_cache": {
  "requests": 14,
  "misses": 2,
  "miss_recache_tokens": 310200,
  "last_miss_at": 1738425230,
  "last_miss_cause": { "causes": ["tools_changed"], "tools_added": 2, "tools_removed": 0 },
  "recache_tokens_if_cold": 45000
}

How

  • Payload only. The widgets read prompt_cache from the status line payload: no transcript scan and no network call. Without it they render nothing. That covers Claude Code before 2.1.251 and the time before the session's first API response.
  • Causes in plain words. tools_changed reads "tools changed (+2)", with the tools added and removed. system_prompt_changed reads "system prompt changed (+120 chars)". ttl_expired_5m reads "5m TTL expired". likely_server_side reads "likely server side". A cause added later shows with underscores turned into spaces. Several causes are joined with commas. A miss Claude Code couldn't diagnose (last_miss_cause: null, or Claude Code before 2.1.260) reads "cause unknown".
  • When each renders. Last Cache Miss renders nothing until the session's first miss. Cache If Cold renders nothing while Claude Code has no figure, as right after a compaction. Cache Misses and Cache Miss Tokens show zero by default and can hide while the session has no misses (h, zero).
  • Formats. Token amounts use the same formatter and number format option as Cache Read and Cache Write. The miss age uses the timers' compact duration (3m, 1h3m), and anything under a minute reads "just now".
  • Shared code. One small shared module, widgets/shared/prompt-cache.ts, reads the object and words the causes. The widgets are four plain classes like their neighbors.
  • Schema. prompt_cache joins StatusJSON. A field in a shape this version doesn't know is dropped instead of rejecting the payload, so a future change to the object can't blank the status line.
  • Builds on fix(cache-timer): use Claude Code's reported cache expiry when present #576. fix(cache-timer): use Claude Code's reported cache expiry when present #576 added part of prompt_cache (warm, caching_observed, ttl, expires_at, hit_ratio, recache_tokens_if_cold) for Cache Timer. Those fields keep their names and types, and the schema adds the rest of the object around them. Cache Timer's code and tests are unchanged.

Demo

Adding the four widgets with the picker's search. The preview shows their samples, the docs' example session:

Powerline

Adding the prompt cache widgets, Powerline mode

Plain

Adding the prompt cache widgets, plain mode

Piped through the CLI in four situations: early in a session, a miss from two added tools, a miss after the 5-minute TTL expired, and a long session whose last miss had two causes. All data is made up.

Powerline

The prompt cache widgets in four situations, Powerline mode

Plain

The prompt cache widgets in four situations, plain mode

Testing

  • On main as of [low] perf(claude-status): hold a lock while the status page fetch is in flight #635 and [medium] perf(git-review): key the PR cache through the cached runGit #639 (62a371b): merges cleanly, bun run lint is clean, and bun test gives 3738 pass, 0 fail. The built CLI under Bun 1.4.4 and Node 26.13.0 matches main in plain and Powerline mode with the baseline configs, and the TUI opens and exits cleanly.
  • bun run lint passes. bun test: 3203 pass, 0 fail.
  • 45 new tests:
    • StatusJSON: every documented field, documented nulls, and malformed fields dropped while the rest parses.
    • The shared module: each cause's wording, zero counts left out, several causes, unknown and missing causes, and age formatting.
    • The widgets: labeled and raw output, zero misses with and without the hide state, missing fields, previews, number format, and registration.
  • Under Node (Vitest), the three new or changed test files pass 45 of 45. Across the full suite, this branch fails the same 18 files as main (spying on Node built-ins), plus ItemsEditor.test.ts's "edits a widget label…". That test is an existing timing flake: it uses fixed 25 ms sleeps, and it passed 3 of 3 runs in isolation.
  • The built CLI under Bun 1.4.2 and Node 26.10.0 gives byte-identical output in plain and Powerline modes. I checked payloads with no prompt_cache, zero misses, several causes, a null cause, an unknown cause, a null recache_tokens_if_cold, and malformed fields. With the baseline configs it matches main, and the TUI opens and exits cleanly under both runtimes.

eric-engberg and others added 4 commits October 14, 2026 20:05
Claude Code 2.1.251+ reports the main conversation's prompt cache
statistics in the status line payload (`prompt_cache`), the same figures
`/usage` shows on its "Prompt cache (main)" line. A cache miss re-sends
content the cache already held, which is what makes usage jump without
anything else changing. Four new Cache widgets show them:

- Cache Misses: misses out of the session's requests (`2/14`)
- Last Cache Miss: how long ago the last miss was and its likely cause
  (`3m ago, tools changed (+2)`; causes need Claude Code 2.1.260+)
- Cache Miss Tokens: tokens the misses wrote back to the cache (`310.2k`)
- Cache If Cold: what the next request re-caches if the cache has gone
  cold by then (`45.0k`)

They read only the payload: no transcript scan and no network. Without
`prompt_cache` (older Claude Code, or before the session's first API
response) they render nothing. Cache Misses and Cache Miss Tokens can
hide while the session has no misses.

The schema drops a `prompt_cache` field in an unexpected shape instead of
rejecting the payload, so a future change to the object can't blank the
status line.
# Conflicts:
#	docs/USAGE.md
#	src/types/__tests__/StatusJSON.test.ts
@sirmalloc
sirmalloc merged commit 8398619 into sirmalloc:main Oct 30, 2026
3 checks passed
pcvelz added a commit to pcvelz/ccstatusline-usage that referenced this pull request Nov 2, 2026
Upstream: weekly limit timers (sirmalloc#768), prompt cache miss widgets (sirmalloc#770), context window size env override (sirmalloc#352, sirmalloc#407), windowed speed fixes (sirmalloc#769, sirmalloc#771), separator/renderer fixes (sirmalloc#773), custom-command styling (sirmalloc#775), clickable cwd links (sirmalloc#744), account email from live credentials (sirmalloc#724), cache timer reported expiry + short toggle (sirmalloc#576, sirmalloc#592), claude-status lock (sirmalloc#635, sirmalloc#772), dedup token metrics (sirmalloc#622, sirmalloc#725, sirmalloc#735).
Not adopted: sirmalloc#765 terminal width from COLUMNS with fixed reserve - the fork keeps flexMode/compactThreshold and tmux pane priority.

Hand edits outside conflicts:
- README.md: kept fork Configure flex separator behavior bullet and older-updates details wrapper; dropped upstream Related Projects additions (claude-eta)
- AGENTS.md: kept fork terminal width wording (sirmalloc#765 not adopted)
- src/types/FlexMode.ts, src/tui/components/TerminalWidthMenu.tsx, src/tui/components/__tests__/TerminalWidthMenu.test.ts, src/tui/components/TerminalOptionsMenu.tsx, src/tui/components/__tests__/TerminalOptionsMenu.test.ts: restored, fork keeps terminal width settings
- src/tui/App.tsx, src/tui/components/index.ts, src/types/index.ts: re-wired terminal width menu and FlexMode export
- src/types/StatusJSON.ts: dropped fork duplicate prompt_cache key, upstream schema is a superset
- src/utils/terminal.ts: COLUMNS source gated on non-tmux so tmux pane width keeps priority
- src/widgets/WeeklyLimitTimer.ts, src/utils/usage-prefetch.ts, src/widgets/__tests__/WeeklyLimitTimer.test.ts: compat fix - fableUsage/fableResetAt -> weeklyFableUsage/weeklyFableResetAt
- src/widgets/ApiUsage.tsx: compat fix - Context Bar honors effective context window (override, clamp, 1.0M format); Weekly renders a valid value despite a session error
- src/widgets/__tests__/ApiUsage.localModelFallback.test.ts: output tokens no longer counted in context (upstream sirmalloc#730)
- src/utils/__tests__/renderer-flex-width.test.ts, src/utils/__tests__/renderer-ansi.test.ts, src/utils/__tests__/gradient.test.ts, src/utils/__tests__/renderer-separator-collapse.test.ts, src/utils/__tests__/config.test.ts, src/tui/__tests__/App.test.ts: fork deviation - fork width model (flexMode, 6-column full reserve, kept settings, Terminal Width menu entry)
- src/utils/__tests__/default-colors.test.ts: fork deviation - accept ansi256:/hex: defaults
- src/utils/__tests__/usage-prefetch.test.ts: fork deviation - weekly usage comes from the API, fork widget imports from ApiUsage
- src/widgets/__tests__/CurrentWorkingDir.test.ts, src/widgets/__tests__/CustomCommand.test.ts: Settings literal gains flexMode/compactThreshold
- src/utils/__tests__/context-window.test.ts, src/utils/__tests__/jsonl-metrics.test.ts: restored fork tests upstream dropped
- docs/test-retirements.md: ledger for the dev-reload snapshot-path and RemoteControlStatus glyph (sirmalloc#605) tests upstream replaced
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants