Skip to content

Commit 0439bec

Browse files
lroolleclaude
andcommitted
v0.18.0: freeze-safe cache expiry, 7d hybrid reset, ≡ cache glyph
Prompt-cache expiry warning that survives idle gaps. Claude Code re-renders the statusline only on activity -- nothing can appear during the idle gap where the cache actually dies, and while active the cache is always ~1 TTL from expiry. So auto stays silent while healthy and speaks only on actual rewrites: - ≡!419k on resume after a gap > TTL (idle-expiry break detection) - Bold red past 200k (premium-band miss) - ≡~ while a large prefix rebuilds - --cache always keeps freeze-safe deadline ≡@15:20 Anchor-slide fix: activity timestamp re-stamps only when usage numbers actually change (vim/permission/model renders carry stale usage). Break hold: 60s wall-clock so debounce can't erase the one frame that caught the rewrite. 7d badge goes hybrid: @nd while >= 24h out, wall-clock @04:00 inside the last day. Fixes the third decaying-countdown bug (after 5h in v0.6 and v0.11). Cache glyph: ≡ (U+2261, triple bar / stacked cache layers) -- one terminal column, slim, terminal-safe, semantically clear. docs/devlog/ gitignored -- working notes, not project content. 266 bats tests, shellcheck -S error clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ba2c350 commit 0439bec

6 files changed

Lines changed: 409 additions & 93 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ sessions/
77
# design mockups (not shipped)
88
assets/palette-retheme-mockup.svg
99

10+
# devlogs — working notes, not project content
11+
docs/devlog/
12+
1013
# local captures and secrets — never version
1114
.cctrace/
1215
.env

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,53 @@
11
# Changelog
22

3+
## v0.18.0 — 2026-07-15 — freeze-safe cache expiry deadline, 7d hybrid reset
4+
5+
**Fix: the 7d pressure suffix no longer decays in a frozen frame.** The
6+
badge kept a relative countdown (`@6h`, `@<1h`) inside the last day —
7+
the same decaying-countdown mistake the 5h badge fixed twice (v0.6,
8+
v0.11), and it showed only under pressure, i.e. exactly when a stale
9+
`@<1h` convinces you you're still capped after relief already arrived.
10+
Now hybrid: day-relative `@Nd` while >= 24h out (decays one day per day;
11+
mild, and the narrowest honest form), wall-clock `@04:00` inside the last
12+
day — the 5h badge's idiom, unambiguous under 24h via next-occurrence
13+
reading, and true no matter how stale the frame is. Zero width cost.
14+
(Resolves the open decision in
15+
docs/devlog/2026-07-15-statusline-refresh-mechanism.org.)
16+
17+
**New: prompt-cache expiry warning that survives an idle gap — "quiet until
18+
it bites".** Claude Code re-renders the statusline only on activity (mount,
19+
new assistant message, mode/model change — no timer; see
20+
docs/devlog/2026-07-15-statusline-refresh-mechanism.org), so nothing can
21+
appear *during* the idle gap where the cache actually dies. And while active
22+
the cache is always freshly ~1 TTL from expiry, so there is no honest
23+
"expiring soon" to render. So `auto` stays silent while healthy — no width
24+
on a deadline that's always ~an hour out — and speaks only when a rewrite
25+
actually happened:
26+
27+
- **`≡!419k` on resume**: the first post-idle render sees activity after
28+
a gap longer than the TTL and reports the rewrite, sized at the re-cached
29+
prefix (observed live: 68min idle -> `read=0 write=87869`, the full
30+
context re-cached at ~20x the read rate; on subscriptions it burns 5h/7d
31+
quota too). **Bold red past 200k** — the premium-band miss. Fires even
32+
when the 300ms render debounce skipped the `read=0` turn, because the
33+
detector keys off the stale activity anchor, not the one-frame collapse.
34+
Held 60s wall-clock so a busy turn's refresh doesn't erase it.
35+
- **Anchor-slide fix**: the activity anchor re-stamps only when the usage
36+
numbers change. Renders also fire on vim/permission/model changes carrying
37+
the previous turn's usage; re-stamping there slid the anchor forward and a
38+
frozen frame would claim a warm cache after it died.
39+
- **`--cache always` keeps the freeze-safe deadline** `≡@15:20` (last
40+
request + TTL) for anyone who wants to read "resume or start fresh?" from
41+
the frozen frame before typing. Wall-clock, never a countdown.
42+
- **TTL default matches how the CLI actually caches** (from CC source +
43+
live traces): claude.ai subscriber REPL sessions get `ttl:"1h"` on every
44+
breakpoint; API-key / custom-endpoint auth stays on the stock 5m cache.
45+
`FORCE_PROMPT_CACHING_5M` / `ENABLE_PROMPT_CACHING_1H` honored. An
46+
observed `ephemeral_1h/5m` breakdown overrides and renders its class as
47+
provenance (`≡:5m@14:25`); the CLI does not forward the breakdown
48+
today. Known gap: subscriber sessions bootstrapped on overage latch 5m
49+
server-side — invisible here; `≡!Nk` still reports the miss.
50+
351
## v0.17.0 — 2026-07-13 — mitm proxy trust, diagnosable !net, unified +X flash
452

553
**Fix: every API fetch died behind a trusted mitm proxy (persistent `!net`).**

README.md

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ Then add to `~/.claude/settings.json`:
6868
## What It Shows
6969

7070
```text
71-
project (main*) +84/-14 1h30m $6.72 opus4.8[1m][███░░░26%] cache:1h@14:20~ [MAX|you] 5h[87%@14:30] 7d[75%@2d] ex[$16.29/$200 8% bal$4.66]
72-
| | | | | | | | | | |
73-
path branch edits time cost model+context cache user 5h quota 7d quota extra usage
71+
project (main*) +84/-14 1h30m $6.72 opus4.8[1m][███░░░26%] [MAX|you] 5h[87%@14:30] 7d[75%@2d] ex[$16.29/$200 8% bal$4.66]
72+
| | | | | | | | | |
73+
path branch edits time cost model+context user 5h quota 7d quota extra usage
7474
```
7575

7676
Every component earns its place:
@@ -84,9 +84,9 @@ Every component earns its place:
8484
| Effort | Compact lowercase badge: `lo` / `md` / `xh` / `max` / `ultra` / `auto` (`high` is the default and stays hidden). Dim for routine levels; `max` / `ultra` use the pressure color; `fast` shows in fast mode. |
8585
| Context bar | Merged with model. Green / yellow / red by window pressure. On 1M models the bar also carries the premium input-pricing band: yellow past 200k tokens, red past 800k — the % alone looks calm (320k = 32%) while every request bills at the premium rate. A real 0% (e.g. right after `/compact` resets the window) renders a visibly empty bar `[░░░░░░0%]` — the snap to empty *is* the refresh signal; the bar hides only when no context data exists at all. Changes flash for ~60s in reverse video: `[█░░░░░30%]+9` while filling, `[░░░░░░3%]-27` right after a compaction. Cost flashes the same way, cents-precise: `$7.90+.37`. |
8686
| User tier | Neutral white-weight (MAX bold, PRO normal, dim otherwise) — identity, never a status color. Truncated display name. |
87-
| Quota | Integer percentages. The 5h badge always carries its reset time while a window is live — `5h[42%@14:30]` reads "42% used, resets at 14:30" — because on a 5h horizon the reset is the number you plan the current sitting around. Wall-clock, not a countdown, on purpose: Claude Code only re-renders the statusline on activity, so a relative "@1h38m" silently decays into a lie during idle gaps, while "@14:30" stays true in a frozen frame. (The 7d badge stays relative — `@5d` decays at day granularity, slower than any idle gap.) When a window's utilization climbs between renders, a reverse-video `+N` token appears right after the badge for ~60s: `5h[44%@14:32]+2` means "you just burned 2%". A drop (window reset) stays quiet — the fresh low number is its own signal. **7d is forecast, not leveled**: a learned per-weekday burn profile (EWMA over your own usage history) plus your recent 24h burn project whether the quota outlasts the window — your heavy Tuesday counts more than a generic average. The verdict is color alone; under pressure the badge shows explicit time remaining in the window: `7d[44%@5d]` red means "at your pace, dry days before the reset 5 days from now". Cold start (<14 days history) falls back to window-average pacing. Recovery color when reset is imminent. **Model-scoped weekly quota**: when the usage API carries a per-model weekly limit (`limits[]`, `kind=weekly_scoped`) for the model your session is running, it renders right after the model+context block — `fabl5[1m][12%] fb[67%]` on a Fable 5 session, `op[33%]` on Opus — because the quota is a property of the model you're running, not of the account-wide 5h/7d cluster. It's a weekly number (same reset as the 7d badge), scoped to one model. Other models' scoped quotas stay hidden: only the limit constraining *this* session is signal. Supersedes the legacy `seven_day_opus`/`seven_day_sonnet` fields, which the API now sends as null. |
87+
| Quota | Integer percentages. The 5h badge always carries its reset time while a window is live — `5h[42%@14:30]` reads "42% used, resets at 14:30" — because on a 5h horizon the reset is the number you plan the current sitting around. Wall-clock, not a countdown, on purpose: Claude Code only re-renders the statusline on activity, so a relative "@1h38m" silently decays into a lie during idle gaps, while "@14:30" stays true in a frozen frame. (The 7d badge is hybrid: day-relative `@5d` while the reset is >= 24h out — decays one day per day, mild and narrow — switching to the same wall-clock `@04:00` inside the last day, where an `@6h`/`@<1h` countdown decayed by the hour exactly when pressure keeps the suffix visible.) When a window's utilization climbs between renders, a reverse-video `+N` token appears right after the badge for ~60s: `5h[44%@14:32]+2` means "you just burned 2%". A drop (window reset) stays quiet — the fresh low number is its own signal. **7d is forecast, not leveled**: a learned per-weekday burn profile (EWMA over your own usage history) plus your recent 24h burn project whether the quota outlasts the window — your heavy Tuesday counts more than a generic average. The verdict is color alone; under pressure the badge shows when relief arrives: `7d[44%@5d]` red means "at your pace, dry days before the reset 5 days from now"; inside the last day it reads `7d[92%@04:00]` — resets at 04:00. Cold start (<14 days history) falls back to window-average pacing. Recovery color when reset is imminent. **Model-scoped weekly quota**: when the usage API carries a per-model weekly limit (`limits[]`, `kind=weekly_scoped`) for the model your session is running, it renders right after the model+context block — `fabl5[1m][12%] fb[67%]` on a Fable 5 session, `op[33%]` on Opus — because the quota is a property of the model you're running, not of the account-wide 5h/7d cluster. It's a weekly number (same reset as the 7d badge), scoped to one model. Other models' scoped quotas stay hidden: only the limit constraining *this* session is signal. Supersedes the legacy `seven_day_opus`/`seven_day_sonnet` fields, which the API now sends as null. |
8888
| Extra usage | Monthly spend, limit, prepaid balance. `--extra auto` shows when quota runs out. |
89-
| Cache health | Detects observed prompt-cache rebuilds and cache-read drops. `cache!` on break, `cache~` when building. If a future Claude Code stdin includes TTL breakdown, `--cache always` can show `cache:1h@14:20`; current stdin usually exposes aggregate cache tokens only. Hidden when healthy by default. |
89+
| Cache health | **Quiet until it bites.** Claude Code never re-renders an idle session, and while you work the prompt cache is always freshly ~1 TTL from expiry — so a proactive "expiring soon" isn't honestly observable, and `auto` spends no width on it. It speaks only when a rewrite actually happens: `≡!419k` the instant you resume onto a dead cache (idle longer than the TTL) or a mid-session prefix collapse — a 419k-token re-cache at ~20x the read rate (and the same burn on your 5h/7d quota on subscriptions). **Bold red past 200k** — the premium-band miss. `≡~` while a large prefix rebuilds. `--cache always` additionally keeps the freeze-safe deadline `≡@15:20` (last request + TTL; a past time in a frozen frame reads "expired at 15:20"). TTL defaults to 1h (claude.ai subscriber sessions) or 5m (API-key auth); an observed usage breakdown overrides it. The `` glyph (U+2261) reads as stacked cache layers — one terminal column, quiet and distinct. |
9090

9191
**Color follows three lanes** so a glance is unambiguous: **status**
9292
(green/yellow/red) = pressure *only* — quota, context, cache, the premium
@@ -112,7 +112,7 @@ red, matching its Claude Code TUI color) = model family; everything else is
112112
| Change flash on every refresh (`+.37` cost, `+9`/`-27` context, `+N` quota) | -- | Yes |
113113
| Model-scoped weekly quota (`fb`/`op`/`sn`) | -- | Yes |
114114
| Works behind trusted mitm proxies (NODE_EXTRA_CA_CERTS) | -- | Yes |
115-
| 265 bats tests + CI | -- | Yes |
115+
| 266 bats tests + CI | -- | Yes |
116116

117117
## Configuration
118118

@@ -161,23 +161,45 @@ when it matters.
161161
### Prompt Cache
162162

163163
```text
164-
--cache auto opus4.8[1m][21%] (healthy, hidden)
165-
--cache auto opus4.8[1m][21%] cache~ (cache is rebuilding)
166-
--cache auto opus4.8[1m][21%] cache! (cache-read collapse observed)
167-
--cache always opus4.8[1m][21%] cache:1h@14:20 (when TTL breakdown exists)
164+
--cache auto fabl5[1m][42%] (healthy: silent — no width spent)
165+
--cache auto fabl5[1m][5%] ~ (a large prefix is rebuilding)
166+
--cache auto fabl5[1m][43%] ≡!419k (resume onto a dead cache: 419k rewrite; BOLD red >200k)
167+
--cache always opus4.8[1m][21%] :1h@14:20 (opt in to the freeze-safe deadline; :1h = observed TTL)
168168
--cache off opus4.8[1m][21%] (disabled; no state writes)
169169
```
170170

171-
Cache state is based on Claude Code's per-turn usage tokens:
172-
`cache_read_input_tokens`, `cache_creation_input_tokens`, and `input_tokens`.
173-
The statusline can prove that a cache was read, rebuilt, or likely broke after a
174-
turn. It cannot know server-side `expire_at` before the next request, so it shows
175-
absolute last-observed cache activity (`@14:20`) rather than a live countdown.
176-
Current Claude Code statusline stdin usually exposes aggregate cache tokens only,
177-
not `cache_creation.ephemeral_1h_input_tokens` / `ephemeral_5m_input_tokens`.
178-
The script accepts those fields for forward compatibility, but TTL class
179-
(`5m` / `1h`) is shown only when observed in actual usage breakdown. Local
180-
environment variables are intent, not proof of what the server accepted.
171+
**Why healthy is silent.** The server refreshes the cache TTL on every request,
172+
so while you work the cache is always freshly ~1 TTL from expiry — there is no
173+
honest "expiring soon" to show, and a deadline that's always ~an hour out is
174+
pure width. The expiry only becomes *real* during an idle gap, and Claude Code
175+
renders the statusline only on activity — so no warning can appear while you're
176+
away. The honest moment is when you come back: the first post-idle render sees
177+
activity resume after a gap longer than the TTL and reports the rewrite as
178+
`≡!Nk`, sized at the re-cached prefix (`≡!419k`), **bold red past
179+
200k** — the expensive premium-band miss. This fires even when the 300ms render
180+
debounce skipped the `read=0` turn, because the detector keys off the stale
181+
activity anchor, not the one-frame collapse; breaks are held ~60s so a busy
182+
turn's refresh doesn't erase them. A cache miss re-caches the whole prefix at
183+
~20x the cache-read rate, and on subscription plans the same multiplier lands
184+
on your 5h/7d quota.
185+
186+
The activity anchor is the last observed *usage change*, not the last render —
187+
Claude Code also re-runs the statusline on vim/permission/model changes with
188+
unchanged usage, and re-stamping there would fake a warm cache.
189+
190+
**The freeze-safe deadline, on demand.** `--cache always` adds `≡@15:20`
191+
(last request + TTL): a past time in a frozen frame reads "expired at 15:20",
192+
so you can decide *before* typing whether to resume this session or start
193+
fresh. Wall-clock, never a countdown — "expires in 43m" rendered an hour ago is
194+
a lie, "@15:20" stays true however stale the frame is. Claude Code's statusline
195+
stdin exposes aggregate cache tokens only, not the `ephemeral_1h/5m` breakdown,
196+
so the TTL is assumed from how the CLI actually requests caching (verified in
197+
traces): **1h for claude.ai subscriber sessions, 5m for API-key /
198+
custom-endpoint auth** (the CLI's `FORCE_PROMPT_CACHING_5M` /
199+
`ENABLE_PROMPT_CACHING_1H` overrides are honored). An observed breakdown wins
200+
and renders its class as provenance (`≡:5m@14:25`). Known gap: a subscriber
201+
session that started while on overage is latched to 5m server-side, invisible
202+
here — the `≡!Nk` badge still reports the miss after the fact.
181203

182204
### Quota Polling
183205

0 commit comments

Comments
 (0)