Decided with the maintainer, 2026-06-03, after the dot's meaning was found
confusing during dogfooding. This is the rationale behind _home_fuel_dot.
The problem we found
The column-2 dot on the home board was overloaded across three orthogonal axes, all crammed onto one glyph + colour:
| axis | where | glyph | meaning |
|---|---|---|---|
| dry / over-quota | tanks, also-available | ! yellow |
this tank is burned out |
| active / current | tanks and continue | ● green / ○ |
"this is the tank you're on" |
| neutral bullet | everywhere | ○ |
just a row marker |
The green "active" dot was the confusing one, for two reasons:
- It meant different things per engine. agy's "active" is a real, global,
persistent fact (the
~/.geminisymlink). claude/codex have no global active — "active" was resolved from the launching shell'sCLAUDE_CONFIG_DIR. So the same green dot was "globally pinned" for agy but "happens to be set in THIS terminal" for claude → unstable, and it leaked an implementation difference (symlink vs env) up to the user. - It was switcher-thinking. "Which account am I currently on, per service"
is the central state of an account switcher. clikae is deliberately not a
switcher — tanks are equal peers in a burn-order list that you pick from each
time. A human seeing claude and agy and codex all green asks "am I on
all three at once?" — because
●/"you are here" is inherently singular, and making it plural breaks the read.
The fix: colour the dot by FUEL STATE (one axis), not by selection
Borrowing the traffic-light metaphor: three colours are not three flags, they are one gauge, mutually exclusive, one reading per tank — exactly like a real signal shows one lamp at a time. The axis is clikae's own identity: can I burn this tank?
| dot | state | source |
|---|---|---|
🔴 red ● |
dry — over limit, can't burn now | limit_tank_dry: transcript (limit_profile_dry), log (limit_log_dry), or a persisted dry marker (dry_store, for exec-only limits like codex) — plus a sibling on the same dry account; verbatim reset string |
🟡 yellow ◐ |
reset passed · unverified | a retained limit whose parseable reset has passed; eligible for burn, pending a successful turn |
🟡 yellow ● |
weekly-% caution (BETA) | the vendor's own "used N% of your weekly limit", captured verbatim + stamped by watch/auto — never computed |
🟢 green ● |
ready — a detectable engine with no bad news | limit_engine_detectable true, not dry/warned |
| ○ (no colour) | no reading — no fuel signal on disk right now (e.g. codex when no limit was caught) | limit_engine_detectable false and not dry |
One sentence: the dot is the engine's own last word about this tank's fuel. Red = "resets in 3h", yellow = "you're at 85% this week", green = "nothing bad to report", ○ = "it has never told us anything" (honest blank — see codex).
Why this dissolves the original confusion
- Multiple greens are now correct, not contradictory: they mean "several tanks have fuel", which is what you want to see.
- "Which am I on" is demoted to where it belongs: the cursor
❯and the burn-order position (momentary, navigational), plus the default-launch-target logic, which stays tied to theactiveflag. We took the colour offactivefirst; the on-row← heretext label it also used to drive was dropped entirely on 2026-06-30 (commit9d55047) — with many shells open on different tanks at once, "which one is THIS shell on" turned out to be noise, not signal.activestill drives the launch target underneath. - ○ is honest. For a long time this recorded that codex's limit was never
written to a transcript at all, so clikae could not passively read it — a
codex tank showed ○ ("no reading"), never a guessed green, and only a live
catcher (
clikae burn) actually seeing the limit in codex's exec output could light it red (persisted to a small dry-until store). That premise turned out to be false twice over — see "codex gets a real light now" below — but the honesty rule it protects is unchanged: still no guessed green, still an explicit ○ the moment codex has told clikae nothing at all (a brand new tank, or one clikae hasn't been asked to read since it last reported usage).
The yellow light is BETA on purpose
The "used N% of your weekly limit" notice is a real vendor signal, but disk has only raw per-project token tallies + the plan tier — no weekly denominator or window boundary — so computing the % ourselves would be a guess (a phantom feature, which clikae forbids). The only honest path is to capture the vendor's verbatim string when watch/auto sees it stream past, cache it stamped, and relay it — the same pattern as the dry detectors echoing "Resets in …".
Unverified prerequisite: it is not yet confirmed that Claude serialises this
notice into the transcript / -p stream (it may be TUI-render-only). So the whole
yellow path ships BETA — wired with a best-guess matcher (limit_weekly_marker)
that the maintainer can dogfood. If the notice never lands in a stream we can tail,
yellow simply never lights (safe default) and we revisit. Marking it BETA is what
makes it testable at all — otherwise the maintainer can't observe it firing.
codex gets a real light now (2026-09-10)
Unlike claude/agy, codex's dot used to be earned only REACTIVELY — a dry marker
after the fact, or nothing. codex's own /status panel shows a PROACTIVE
reading whether or not anything is exhausted yet ("5h limit: [████] 100% left
(resets 05:14)", "Weekly limit: [████] 95% left (resets 22:12 on 15 Sep)"), and
clikae had no light for that at all: clikae burn codex … --json printed
"reset": null on a run that never hit anything, even though codex knew
exactly when the window resets.
Source chosen: codex's own rate_limits object, persisted into the rollout
transcript. Every codex session — headless codex exec included, confirmed
on a real rollout whose session_meta.originator is codex_exec, not just
the interactive TUI — writes a token_count event carrying
rate_limits.primary/.secondary, each already resolved by the SERVER to a
used_percent and an ABSOLUTE resets_at epoch. That is cheaper and more
reliable than scraping the rendered progress-bar text: no local-time guessing
at all, because the server already did that math and clikae only relays it —
the same "relay the vendor's own number, never compute one" rule the yellow
BETA light above lives by, except here the vendor's number is genuinely on
disk instead of a phantom feature waiting to happen.
Only that structured source ships. An earlier revision of this feature also
carried a text-shape parser for the RENDERED status line (two grammars, no
explicit zone, since codex always renders in the machine's own local
wall-clock) meant for "a captured status line where the structured source
doesn't reach" — a burn log, or a manual $CLIKAE_LIMIT_PATTERN-style paste.
It was removed in round-1 review (2026-09-12): nothing in lib/, bin/, or
scripts/ ever called it, only its own tests did, so it was ~130 lines of
permanently-untested code promising a capability no path in clikae could
actually reach. If a real caller for a captured status line shows up later,
it can be rebuilt against _limit_codex_render_reset's epoch→phrase
direction, which does ship (see below).
🔴 Do not assume primary = 5h and secondary = weekly by POSITION. A
real free-tier account reported
limit_id:"codex" with a 30-day (window_minutes:43200) window living in
primary and secondary always null — nothing like the 5h/weekly split a
different plan's /status shows. Each side is labelled by its OWN
window_minutes (_limit_codex_window_label: ≤360min → 5h, ≤10080min →
weekly, else <N>d), never by which JSON key it arrived in.
🔴 A window's own resets_at in the past means it has REFILLED, not that
it is still at its last reported percentage. rate_limits is a snapshot
written at the moment of that token_count event; once resets_at passes,
the server has reset that window server-side, and the used_percent sitting
next to it describes a quota that no longer exists. A tank that burned to
100% at 08:00 with a 5h window resetting at 12:00 must read green/"100%
left" again at 16:00, not the red/"0% left" its last-known event still says
on disk — that exact case (a full tank read hours after its own reset,
still lighting red with a reset time already hours in the past) was round-1
review's P1-1 finding, since fixed: _limit_codex_window_expired treats any
side whose resets_at is more than 60s behind now as fully refilled (0
used / 100% left, no reset text), before the light or the rendered reset are
computed from it. The light therefore always follows the tighter of
whichever windows are still genuinely valid, and a past instant is never
rendered as if it were a future one.
Light = the tighter window. Thresholds are on percent LEFT, the same unit
codex's own text uses: 0% left is red (can't burn now, same meaning as every
other red dot), under 15% left is yellow, otherwise green — computed from
WHICHEVER of the two windows is closer to exhausted, so "5h: 90% left,
weekly: 0% left" reads red even though the 5h window alone looks healthy. No
data at all (a tank codex has never reported on) stays the honest ○/dim
"no reading" — never a guessed colour. clikae burn codex … --json's
"reset" field now carries the tighter window's own rendered reset text
instead of always null on a healthy run; the board's note (clikae tanks)
shows both windows side by side.
Cache
$CLIKAE_HOME/cache/weekly/<cli>-<profile> — first line = the verbatim vendor
phrase, written by the watch/auto capture, read by _home_weekly_read. Absent =
no reading = the tank falls through to green/○.
$CLIKAE_HOME/cache/codex/<profile> — two lines: a cache key (the rollout
store's own file count + newest mtime + TOTAL byte size) and the raw
rate_limits fields last read from it, written/read by
limit_codex_status_cached (_limit_codex_rate_limits_cached,
lib/core/limit.sh). This replaced an earlier "no cache of its own,
self-refreshes on every read" design: that premise was true only on the
maintainer's own small store (6 rollouts, 5 MB); round-1 review (2026-09-12,
P2-1) measured limit_codex_status at ~1.5s per call on a synthetic
120-rollout (~62 MB) store, because it re-scanned every rollout file's
CONTENT on every board redraw — breaking _home_fuel_dotv's own "fork-free"
contract. The cache is keyed by the store's mtime/count/size, not a TTL: a
redraw with no new codex activity since the last read costs one find + one
stat + one wc -c, never a re-scan of file content. Light/note/reset
themselves are never cached — they depend on now (see the P1-1 note
above) — only the raw vendor fields are, recomputed into a reading fresh on
every call.
🔴 Size joined mtime in the key (round-2 review, P1-1). codex appends to
the SAME rollout file rather than opening a new one per event, and mtime
alone is SECOND-resolution — a second token_count write landing in the
same wall-clock second as the read that built the cache was invisible to a
file-count+mtime-only key, so the board kept serving a stale reading
indefinitely (reproduced against a real board: persistent false green on a
tank already at 0% left). An append always changes the store's total byte
size even within the same second, so size closes that gap.
$CLIKAE_HOME/cache/codex/<profile>.d/<rollout-basename> — one small file
PER rollout (round-2 review, P2-2), keyed the same way
(_limit_codex_file_state: that file's own mtime + size) but scoped to a
single file rather than the whole store. The whole-store cache above is
still the fast path for an IDLE tank (one find+stat+wc -c and nothing
else); on a whole-store miss, _limit_codex_rate_limits_cached falls back to
these per-file entries instead of re-scanning every rollout — a redraw
during ACTIVITY (a burn appending to one rollout every few seconds) then
only re-scans that ONE file, reusing every other rollout's cached reading. A
rollout with no rate_limits event of its own caches a "none" marker too,
so a tank that has never reported usage doesn't pay a fresh scan of every
rollout on every redraw either. Round-1's whole-store-only cache made the
MISS path (any activity) costlier than the pre-cache code — 2.83s vs 1.68s
on the 120-rollout synthetic store, because the miss path re-scanned every
file AND (round-2's P2-1 finding) every one of those scans silently read
each file whole under bin/clikae's real pipefail — the per-file cache
plus the pipefail fix together bring an ACTIVE-tank redraw back down near
the pre-cache cost of scanning just the one changed file, while an IDLE
redraw stays at the whole-store fast path's cost.
Expired limit evidence (#75)
The shared tank verdict resolves reset phrases against the original observation
timestamp, then compares that instant with the current clock. This prevents an
undated time from rolling forward every day. A zone suffix in the phrase,
(Asia/Tokyo) and friends, is always authoritative when present — including
for codex's try again at 5:23 PM grammar — because agreeing with it on the
maintainer's machine and disagreeing on a traveller's is exactly what a zone
suffix exists to prevent (round-1 review, #75). Only when the phrase names no
zone at all does codex fall back to rendering in the observer's own local
timezone. Unparseable phrases keep the existing behavior. Live output detection
still reports a newly observed rejection as dry.
An expired limit is yellow ◐, “reset passed · unverified”, taking precedence
over proactive percentage snapshots. The default dry batch excludes it, allowing
burn's next-tank selection to retry it. The board and status request the same
batch with cautions included; status exposes the note as fuelNote in JSON.
A later successful transcript turn clears the caution — including, for codex, a
turn observed in the transcript after an unrelated persisted marker was written
(round-1 review, #75: falling straight through to the marker regardless of a
transcript's own recovery could pin a tank yellow forever). That clear is
timestamp-gated, though (round-2 review, #75): a headless codex exec limit
never reaches the transcript at all, so a days-old interactive recovery must
not erase a marker burn wrote moments ago — only a recovery observed AFTER the
marker's own timestamp clears it; an older recovery next to a newer marker
falls through and the marker's own TTL / CLIKAE_DRY_MAX_RETAIN cap governs.
Parseable expired store evidence survives the marker TTL as unverified until
cleared, but never longer than CLIKAE_DRY_MAX_RETAIN (7 days) — retained
evidence is a caution, not a promise to remember forever. Unparseable store
evidence retains the existing TTL behavior.
Vendor usage cache (#72)
Run clikae usage [engine] [tank] [--json] to read usage. JSON output is one
object per tank: engine, tank, window_pct, weekly_pct,
window_resets_at, weekly_resets_at, and source. Percentages are used
quota (0–100); unavailable fields are null. source is "vendor" (a live
call actually answered), "transcript" (derived from local evidence the
engine already wrote — currently Codex's rate_limits, scanned from
rollouts modified in the last 7 days, the same -mmin -10080 window
limit_codex_status uses), "expired" (#107: the vendor refused an access
token whose credentials hold a refresh token, or that token's own recorded
expiry had passed — the login is fine, only a session refreshes it; always
with reason:"expired-token" and no numbers), or "unknown" (no usable
reading; reason is no-credentials, network, rate-limited or
unparseable when known). A rate-limited reading (#136: HTTP 429, which
used to be part of the network lump) may carry retry_after, the vendor's
own Retry-After header in whole seconds, and only when that header was in
1..86400 — clikae watch's usage poll schedules that tank's next poll off it
instead of doubling its own backoff. A VENDOR reading may carry models
(#137): the per-model weekly rows the vendor sends in limits[] as entries
with kind weekly_scoped, normalized to {name, pct, resets_at}. Nothing on
the board picks among them — the dot stays on the all-models number, because
choosing the relevant per-model row would require knowing which model a tank
runs and a tank carries no such property (--model is an argument to
burn/relay). #149 reads them for one thing only: a row whose pct is above
the tank's weekly number is the binding limit, so the note names it
(weekly 87% · Fable 100%, the name being the vendor's), and a row at 100%
turns a green dot yellow. A row at or below the tank value, or no models[],
adds nothing. A reading with no numbers shows the last one that had them,
aged, with a reason word (expired, no-probe, no-signal); see
docs/usage.md.
On the board an expired reading under 24h old draws the no-reading · with
the note expired · usage --wake <tank> (the word lives in the note, not the
dot — no emoji on any delivery surface, so the note carries a word and costs
no special width handling; and the note is the short form because a tank row
leaves 33 columns for it on an 80-column terminal — clikae usage prints the
full sentence, token expired — run a session or 'clikae usage --wake <tank>'). The tmux status row, which has a fuel SLOT rather than a padded
grid, draws the word expired in that slot instead of the dot, on the same
24h ceiling, measured like any other ASCII fuel string
(docs/DESIGN-tmux.md Rule 11 §6/§7). The optional
adapter hook is adapter_usage <config-dir>. Claude calls the vendor OAuth
usage endpoint; Codex never runs a codex process for this — it reads the
same rollout transcript evidence limit_codex_status does, so its source is
"transcript" and its cached_at is that reading's own newest event
timestamp, not the time it was read (round-1 review, P2-4: a week-old
rollout must not be stamped "just now"); Antigravity currently returns
unknown. Parsing requires jq; without it readings are unknown.
Readings live at $CLIKAE_HOME/state/usage/<engine>/<tank>.json, including
cached_at (the reading's own evidentiary timestamp — see usage_read's
header) and scanned_at (when it was actually fetched/scanned; round-2
review, P3 — the two coincide for a vendor reading but not for codex's
transcript evidence, whose cached_at is the underlying event's own time).
TTL defaults to 120 seconds against scanned_at; CLIKAE_USAGE_TTL
overrides it and --fresh bypasses it. Errors are cached too — except that
an "expired" reading is cached for at most _USAGE_AUTH_FAIL_TTL_SEC (60),
so the read after a session refreshes the token sees numbers (#107). Writes are
atomic, and usage_read is the ONLY writer in the repo.
Who writes this cache, and who reads what (round-2 review, P2-1):
- (a)
burnrefreshes the LAUNCHED tank's reading once, at run END — after the run's own artifact check (so it can never delay judging that run's outcome), never before launching (the launch itself still pays zero vendor round-trips — round-1 review, P1-2/P1-3/P1-4, unchanged). - (b) when the named tank is dry and burn must reroute, this is the one
moment a stale number would cost burn a wrong hop — so surviving
CANDIDATES are ranked FIRST, on whatever is already known (their cache,
through
usage_cache_peek's own age ceiling — see below), and ONLY THEN does burn spend live vendor calls verifying the candidates ranking says are worth a call, bounded by_BURN_REROUTE_REFRESH_CAP(3 by default; named once inlib/commands/burn.sh, never re-typed; an override that is not a non-negative integer of at most 9 digits warns loudly and falls back to the default rather than silently spending zero calls — round-5 review P3-6 for the non-numeric case, round-6 review P3-4 for the all-digit OVERFLOWING one, which passed the first check and then produced the same silent zero budget because[ "$calls" -lt 99999999999999999999 ]is an arithmetic overflow, not a comparison._USAGE_CACHE_PEEK_MAX_AGE_SECbelow carries the same bound, where an overflow was the quieter failure still: it became a 1e20-second ceiling, i.e. every reading trusted forever, with no warning at all). Round-3 review, P3-4, foundcandidates * --max-time 8has no total bound as a fleet grows — the cap answers that — but round-4 review, P2, found the round-3 shape spent that cap on the first 3 candidates by LISTING (alphabetical) order, refreshed BEFORE ranking existed: the calls landed on tanks that could never win while the tank that DID win was routinely the one candidate left unverified with a stale, flattering on-disk reading. Ranking first and spending the budget on the winning candidate(s) closes that: a same-account sibling was already collapsed to one candidate before any live call (Pass 2, below), so this can never spend two calls on one account either — one refresh per account, reusing that one reading. A candidate the cap never reaches keeps whateverusage_cache_peekalready returned for it (fresh, aged, or unknown past the ceiling) — never a live call, same as today. Refresh priority (round-5 review, P3-4) has FOUR levels, not three: a tier-1 candidate WITH an on-disk reading too old for the ceiling below goes first (the one case a confident-but-fresh candidate could be hiding an even better tank the board itself still shows a stale percentage for), then confident tier-0, then a blank tier-1 with no reading at all, then tier-2 last. And the moment a refresh confirms a verified 0% window (an unbeatable floor — nothing left in the pool can score lower), the loop stops spending the remaining budget rather than always burning every call in the cap regardless (round-5 review, P3-3). A refresh call that FAILS (401, network error, expired token) may only ever rank that candidate the SAME or WORSE than the evidence already on disk, never better (round-5 review P2-1, round-6 review P3-1). Concretely: a snapshot tier-0 candidate (known <90%) becomes unknown — it must never win on the flattering stale number Pass 1 read before the call proved it unreadable — while a snapshot tier-2 candidate (known >=90%) KEEPS its last good reading and stays tier 2. Blanking tier 2 would have PROMOTED it, because unknown outranks known->=90% by design (see the three tiers above): a tank last read at 99% sixty seconds ago whose token is dead this call would have beaten a sibling that verified clean at 95% in the same call. The kept reading is already bounded by the age ceiling below (Pass 1 applied it, so anything older was unknown here to begin with), so this never resurrects a number the ceiling had discarded.usage_readoverwrites that candidate's on-disk cache withsource:"unknown"on the same failure either way: the disk records "cannot read it now", the ranking records "no better than what we last saw".usage_cache_peek's own age ceiling (_USAGE_CACHE_PEEK_MAX_AGE_SEC,lib/core/usage.sh, 15 minutes by default) is the second half of the round-4 fix: a reading older than that is "unknown" for ranking purposes, never a flattering-but-stale percentage — belt-and-suspenders with the reorder above, since a reading young enough to survive the ceiling can still legitimately outrank an unverified tier and get prioritised for a live call, the same as any other tier-0 candidate would. This ceiling measures the evidence's owncached_at, notscanned_at— see the age-clock paragraph below for why that distinction matters for codex's transcript-derived readings specifically. A non-numeric override here warns loudly and falls back to the default rather than silently reading every candidate as unknown (round-5 review, P3-6). - (c) the board NEVER fetches. It reads whatever is already on disk,
however old, via
usage_board_fields— silently within the TTL, WITH its age alongside it ("window 44% · weekly 20% · 3h ago") once past the TTL, and treated as if there were no cached reading at all once it is 24h or older.clikae usage [engine] [tank] [--fresh](a bareclikae usage --freshcovers every tank) is the only thing a HUMAN runs to fill this cache; (a)/(b) areburncalling the same writer (usage_read) the same way, on its own schedule. - (d)
usage_cache_peek(burn's ranking) andusage_board_fields(the board's display) both honour the reading's OWNwindow_resets_at/weekly_resets_at: a window whose reset instant has already passed reads as 0% used, never as whatever stale percentage the last fetch happened to record — a tank that ran dry at 15:00Z must not still be ranked (or shown) at its old 100% two hours after its window reset. This "reset passed -> 0%" rule only fires forusage_cache_peekWITHIN its own age ceiling (below) — past that ceiling the reading is "unknown", never "0%": a reading too old to trust for ranking is also too old to know it hasn't drifted past a LATER reset it never recorded (round-5 review, P2-2, found the ceiling itself measuring the wrong clock let this combination produce a false "0%" for a 3-day-old codex transcript — see the age-clock paragraph below).usage_board_fieldscarries no such ceiling: it honours the reset-passed rule at any age, gated only by its own 24h cutoff in (c). Round-5 review, P3-4/P3-3, also changed WHICH candidates Pass 4 spends its live-call budget on and WHEN it stops: a tier-1 (unknown) candidate that DOES have an on-disk reading — just one too old for the ceiling below — is now refreshed BEFORE a confident, fresh tier-0 candidate (a candidate with no on-disk reading at all still ranks behind confident tier-0, only ahead of tier-2), so a tank the board still shows a stale percentage for can't sit unverified forever behind cap-many fresher-but-not-necessarily-better candidates; and the loop stops the moment a refresh confirms a verified 0% (an unbeatable floor) rather than always spending every call in the cap. - (e) a LIVE SESSION refreshes its own tank, from the
wakewindow it already has (2026-09-22). (a)–(d) between them left the refresh UNOWNED on a machine where nobody burns: measured, a nine-day-old cache and a tmux status row showing the no-reading·the whole time, while a machine burning all day showed live numbers — oneclikae usage <engine> <tank>took 0.7s and the row read5h 25% · 7d 10%on the next redraw. So the session that is spending the quota is the one that keeps the reading current: oneusage_readat launch, backgrounded, for a session clikae just spawned (wake_usage_prime, called fromlib/commands/switch.sh), and one everyWAKE_USAGE_INTERVAL(300s) from the watcher's own loop (wake_watch,lib/core/wake.sh). Same writer, same terms —CLIKAE_USAGE_TTLstill applies, so a cadence shorter than the TTL would only re-read the cache — and it keeps that file's constraint: no daemon, no state file, nothing that outlives the session, and no model of anyone's quota. The tmux row itself still NEVER fetches (tmux_status_fuelv;docs/DESIGN-tmux.mdRule 11 §3 carries the same receipt), and a refresh that fails is silent: the last reading stays on disk and the row's own age suffix says how old it is. A tank nobody has a session on is unaffected — it ages on the board exactly as (c) describes.
Three different clocks answer three different questions here — reconciled, not unified, because unifying them would make one of the three lie (round-5 review, P3-5):
One thing they are NOT allowed to disagree about is what a NEGATIVE age
means. A cached_at in the FUTURE — a cache written while the host clock was
ahead, or copied from a machine that was — counts as age 0, in both
rulers (round-6 review, P3-7). Until then usage_cache_peek REJECTED such a
reading (select($evidence <= $now)) while _home_fuel_dotv_compute clamped
it ([ "$age" -ge 0 ] || age=0), so a cache stamped thirty seconds ahead was
"unknown" for burn ranking and "freshly read" on the board at the same
instant — a fourth kind of inconsistency this section did not cover. Age 0
rather than rejection, because the reading is real evidence carrying a skewed
stamp and rejecting it punishes the tank for its host clock; and because a
skewed stamp can only make a reading look YOUNGER than it is, never older, so
the rule can never resurrect a reading the ceiling would otherwise discard.
On the board, age 0 also means no age annotation is printed, which is the
honest rendering: the stamp says "now" and we have no better number.
_USAGE_CACHE_PEEK_MAX_AGE_SEC(lib/core/usage.sh, 900s / 15 minutes) answers "is this reading recent enough to RANK burn's reroute on". It measures the evidence's owncached_at(round-5 review, P2-2 — NOTscanned_at: a codex transcript reading'scached_atis the underlying quota EVENT's timestamp, whilescanned_atis merely when something last re-read that same unchanged rollout off disk; measuringscanned_atlet an indefinitely-old rollout stay ranking-eligible forever just by being rescanned, no new evidence from the vendor ever required). Short on purpose: burn's ranking is a live decision made once, right now, so a number a burn might act on immediately should be barely older than "now"._home_fuel_dotv_compute's 24h cutoff (lib/commands/home.sh, 86400s;_home_fuel_dotvis the memoizing wrapper around it) answers "is this reading still worth SHOWING a percentage for on the board at all". Long on purpose: the board is a passive glance, not a live decision — a number from this morning is still useful context next to a dot, where a number from 15 minutes ago being ranking-stale would be useless noise if the board refused to show numbers past the same short ceiling burn uses.next_tank(lib/core/profile_store.sh:508) answers neither question — it carries no percentage and no age at all. It walks the burn-order RING bylimit_tank_dry's boolean dry/not-dry state (a completely different subsystem,lib/core/limit.sh, unrelated to the usage cache these two clocks measure) and stops at the first same-engine tank that isn't dry. Giving it either of the above ages would require it to start reading the usage cache it was never built to read — a materially different, larger change, not a two-line reconciliation.
usage_cached_fields (fresh-only, no age shown) still exists with its
original contract for any caller that genuinely wants "fresh or nothing",
but is no longer the board's primary read — see (c) above for why a
120s-TTL-only gate left the vendor cache invisible almost all the time on
a real machine (round-2 review's own receipt: 3 of 4 real tanks, hours
stale, showed nothing).
_home_fuel_dotv's header used to promise the redraw path is "fork-free";
that was never fully true (see the codex paragraph above) and is even less
so now that (c) means every tank not yet memoized this redraw pays one
jq fork to parse its cache file (plus one shared date fork for the
whole redraw) — see that function's own header (round-2 review, P3-1) for
the measured cost and why a hand-rolled bash-only JSON reader was judged
not worth it for a sub-millisecond-per-tank, redraw-only cost.
Dry and the expired-limit caution are decided FIRST, from _home_is_dryv —
a persisted dry marker, an account-contagion sibling, or a parseable reset
that has already passed (reset passed · unverified) all win outright,
verbatim reset string included, before the vendor cache is even consulted
(round-3 review, P2-1: the reverse ordering let any <24h cached reading
paper over a dry tank). The vendor reading only colours a tank that has
already cleared both checks: for a current (<24h) reading on such a tank,
window and weekly are judged separately (2026-09-22 decision), not by
peak = max(window_pct, weekly_pct) — they cost differently: a full 5h
window means "wait up to two hours", a full week means the tank is gone for
days.
| dot | condition |
|---|---|
🔴 red ○ |
transcript-dry (unchanged, above), OR window ≥ 100, OR weekly ≥ 100 |
🟡 yellow ◐ |
weekly ≥ 85 (dispatch should already be moving to another tank — one step before the fleet's own "stop burning a shared tank at 90%" rule), OR window ≥ 90 (a burn dispatched now will probably die mid-run) |
🟢 green ● |
otherwise |
The note keeps carrying both percentages verbatim, unchanged ("window N% · weekly N%"). Missing readings, and readings 24h or older, are treated as unknown and fall through to the existing weekly-caution / codex-status / ready chain below.
The tank a caller names is always the one burn launches — there is no
pre-launch substitution (round-1 review, P1-2/P1-3/P1-4). Headroom
preference only governs which tank a dry burn reroutes to next, in
three tiers, best first: a known reading under 90% used beats an unknown
reading, which beats a known reading of 90% or more (P2-9 — a tank we know
nothing about should not lose to one the vendor just called nearly
exhausted; tiering itself uses peak = max(window_pct, weekly_pct)).
WITHIN a tier, ordered by lowest window_pct first, weekly_pct only as
the tie-break (round-2 review, P2-3 — swapped from round-1's weekly-first
shape: a burn is about to run NOW, against the 5-hour window, so a tank
with a great weekly number but its window nearly spent is the wrong pick).
Tanks sharing a vendor account rank as ONE, using the worst (highest)
reading any of them reported this call — never overstating a shared quota
because one sibling's cache snapshot happens to look better — and a tank
is never offered as the very next hop after a sibling on the same account,
even before that account is confirmed dry (P2-6). The existing
live-session, busy-burn, solo, and dried-account exclusions are retained.
--to always wins outright over this ordering, and every hop it produces
— including the very first, off a tank that just went dry — is recorded
in rerouted_from. Unknown usage keeps the existing transcript-based
launch and reroute behavior otherwise.
Claude reads the tank credential file or tank-specific macOS Keychain
service, guarded by command -v security and a bounded wait via
lib/core/timeout_bin.sh's _burn_timeout_bin — timeout → gtimeout →
perl -e 'alarm …; exec …' → an honest warning and an unbounded call as a
last resort (mirroring the credential-migration hook's own guard; round-1
review, P2-7; round-2 review, P2-2: this used to be its own two-arm copy,
timeout/gtimeout only, right here — the ONE platform this branch runs
on, stock macOS, ships NEITHER by default, so the bound was silently empty
on an unmodified install; now it calls the repo's one shared resolver,
which already had the third arm). The bearer token is passed solely
through curl configuration on stdin, with shell tracing disabled, never
through argv or an exported variable. Curl defaults are disabled and
timeouts bound failures; HTTP errors and network failures become unknown
(or expired, above) without printing response bodies. --fail stays; the
HTTP status alone is written by -w '%{stderr}%{http_code}' to a private
temp file, which is what makes a 401 observable rather than inferred (#107).