Repository navigation
feat(telemetry): read out Claude Code's prompt cache state per session - #607
Conversation
Claude Code's statusline has carried a prompt_cache object since v2.1.251, and Codeman's statusline exporter already received it, but StatusTelemetrySchema stripped the whole object, so Codeman never knew whether a session's conversation was still cached. Keep it and show it. StatusTelemetrySchema now lists every documented scalar field null-tolerant (several are documented as possibly null, and the object gains fields across CLI versions, so a strict shape would 400 the post and blank the footer), while parsePromptCache normalizes only the subset Codeman stores, so a hostile localhost payload cannot bloat state. Session.promptCache rides toState with a change-only session:promptCache event, and the route broadcasts the stored object. The Session Options (Respawn tab) readout shows warm-until or cold with the re-read stake, the cache hit rate, the last miss's causes, and, for Claude Code's own idle compaction (recache_tokens_if_cold null while expected_rebuilds rose), a "compacted at HH:MM" note that is never raised as an alert. setPromptCache stamps that on a new object rather than mutating the parsed report, so the stamp reaches the broadcast. Sessions without a statusline of their own also get a cache:until HH:MM / cache:cold group in the terminal footer. The last report is restored on a Codeman restart (like displayModel) so the readout is not blank until the pane's next render; the readout is time-aware, so a value whose expiry has passed reads as cold, not stale-warm. A launch without the exporter records why (collection off, workspace statusline, remote) so the row says the cause instead of "not reported yet" forever. This is the sensor a later opt-in keep-warm would read, and the TTL it reports is the source of truth rather than the plan tier.
|
Thanks @JDProfresh, this is a careful first step for #554. It keeps Claude Code's One thing needs to change before merge, and it's on me, not you: the compaction signal I asked for in #554 doesn't exist in that shape. 1. The "Compacted at" stamp never fires against a real Claude Code ( In #554 I wrote that
So no single report carries both "null" and "rose", and
The signal that does hold is entering the null window: a warm report with const inWindow = status.warm && status.recacheTokensIfCold == null;
if (inWindow && prev?.recacheTokensIfCold != null) compactedAt = Date.now();
else if (inWindow && prev?.compactedAt != null) compactedAt = prev.compactedAt;That stamps when Then please:
If you'd rather keep this PR small, dropping Smaller things, all quick:
Once the compaction signal is fixed (or removed), I'll merge. Everything else here is ready. |
Claude Code's tracker nulls recache_tokens_if_cold at a compaction or a tool-result clearing and only counts the expected rebuild at the next request, which also closes the window, so no report carried both and the old rule never fired. Stamp on the first warm null report after one that carried a value, carry it through idle re-reports and drop it at the next request; the readout now says "Context compacted at HH:MM" since a clearing opens the same window. Also persist a changed report so a crash restores the latest one, rename the session field to statusLineExporter (statusLineTelemetry is the registry capability) and document its values, word the stake as re-cached, move the readout block above the SSE staleness heading, and drop keep-warm mentions until it lands.
|
Thanks for replaying it against the real order. I checked the 2.1.294 binary here and see the same thing:
Full suite green locally (545 files, 10,695 tests). |
…#607 merge-time fix) Two comments still described the abandoned expected_rebuilds rise rule; setPromptCache no longer reads that field for the compaction stamp. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks @JDProfresh, shipped in 1.41.1. All six points from the review were in. At merge I only fixed two comments that still described the old |
This is step 1 of #554: keep the prompt_cache object Claude Code puts on the statusline and show each session's cache state. It is display-only, no behaviour change, and it stands on its own. The opt-in keep-warm from the discussion is a separate later PR.
What it does
Claude Code's statusline has carried a prompt_cache object since v2.1.251, and Codeman's statusline exporter already received it, but StatusTelemetrySchema stripped the whole object, so Codeman never knew whether a session's conversation was still cached. Now it keeps it and shows it:
Design, against your asks in #554
I checked the field names and null-ability against the Claude Code statusline docs (code.claude.com/docs/en/statusline) rather than the summary in the thread. One note: the docs give ttl_expired_5m as the example cause name; the parser keeps whatever strings arrive, so ttl_expired_1h works the same. Everything else matched.
One known limitation, stated rather than engineered around: the route applies reports in arrival order, and when the exporter wraps a user's own statusline it backgrounds the POST, so two sub-second loopback reports can land out of order and a stale one briefly overwrites newer state until the next render corrects it. For a display-only readout that is acceptable.
A spawn-time detail from the same change: a launch without the exporter (plan-usage collection off, a workspace that sets its own statusLine, or a remote/Docker session) never reports, so the row says the cause instead of "not reported yet" forever.
Tests
The full suite is green (545 files, 10695 tests; the browser, mobile, and perf suites are the usual environment-gated exclusions).
Docs
api-reference.md gets a Prompt cache (promptCache) section next to the existing Session model one, documenting the field and the session:promptCache event. The feature wiki (Keeping-Agents-Running), the design note (usage-limits-display-plan), CLAUDE.md and architecture-invariants are updated in the same change.
Verified
Observed, on a throwaway instance running Claude Code 2.1.294:
Also observed, on my dev install running this together with the other open PRs of this batch on 1.41.0 (
aa3730aa), from a real statusline rather than posted payloads: after one turn of a Claude Code 2.1.294 session the session reportedwarm: true,ttl: 1h, about 35k tokens at stake and a 100% hit ratio, and Session Options read "Warm until 05:05 PM (1h cache). About 35k tokens would be re-read if it goes cold. 100% cache hit rate." With plan-usage collection switched off, the session instead reportsstatusLineTelemetry: collection-off, so the row says why it is empty.Inferred, not waited out live: the warm-to-cold flip at expires_at (covered by the unit tests, which render cold once now passes expires_at; and the statusline re-runs on its own at expiry per the docs). An earlier cut of this (the readout and the footer, without hit_ratio, the compaction note and the restart restore) has been running on my own install since 2026-10-07.