headroom v0.4.0 — the menu-bar app
The fleet now lives in your menu bar: a little head that fills like a battery.
Native menu-bar app (macOS, Tauri)
integrations/menubar — a real click-down panel, not a text menu:
- The tray icon is the reading. A minifig-style head that fills like a
battery with your fleet's average 5-hour battery (a current window
contributes its level, a capped one an honest zero). Drawn at runtime,
redrawn every minute. A dash means no live reading — and the icon applies
the same fail-closed freshness contract as every other surface before it
trusts a single number: exact schema, current freshness, sane clocks,
live accounts only. - It drops down like a system panel. Right edge under the icon, native
rounded corners, dark panel, no chrome. The liquid-glass widget page
renders straight onto it. - It never trades your data for an error page. Once the widget has
loaded, a reachability blip keeps the last known view (which greys itself
out honestly); the "is the tunnel up?" screen only appears on a cold start
with the server genuinely unreachable. - Viewer, not a data path. Loopback-only navigation with a strict
allowlist, no IPC granted to the page, no telemetry. The one network read
beyond the page itself is the public/widget.jsonprojection, over the
same numeric-loopback-only socket as the reachability probe — bounded,
single-flight, size-capped.
Build it with cargo tauri build (Rust 1.88+); see the
menubar README.
Codex conversation handoff
headroom handoff now moves a Codex conversation between codex homes,
with the same transactional guarantees as the Claude path: global lock,
no-clobber publication, crash-recoverable staging, source never modified.
--headless BATON runs codex exec resume on the target for automation.
The averages headline
The glanceable number everywhere — popover, SwiftBar, Übersicht cards — is
now the fleet's average battery (5h big, 7d compact), not the fullest
single tank. Limited windows count as an honest 0; held/stale readings never
move an average. Model-scoped weekly windows (Opus, Fable, …) now ride along
as extra per-account rows, without ever driving the account state — a scoped
cap doesn't block your other models.
Display integrity (three real-world bugs)
All three were found by actually running the widgets against a live fleet:
verified_localslots rendered as held. The display layer held any
account not verified via the provider — while the router happily routed
it. Display now accepts exactly the trust states routing accepts.- A failed refresh attempt demoted everything. An inline refresh racing
another collector flashed every surface to "held, never promoted to live"
with a seconds-old snapshot on disk. Demotion now happens only when the
snapshot is genuinely outside the freshness window — andusage.json
carries a non-demotingrefresh_attempt_faileddiagnostic so a dying
collector is visible early. - 34 milliseconds of clock skew held the entire fleet. The freshness
guard treated any futureevaluated_atas untrustworthy — including
sub-second NTP drift between server and viewer. It now tolerates 10
seconds of skew; genuinely future-dated feeds still hold.
A rate-limited rate-limit check no longer strands you
Reported by a user on X: when the usage endpoint itself returns 429,
headroom held the account and refused to launch — "no account has proven
headroom" because the meter was busy. collect now carries the last
verified reading through the throttle window: age-bounded by the same
observation window as every reading, identity re-verified at routing time,
and only when the slot's current credential binding still matches the old
row. Expired or rejected cached tokens also hold with actionable codes
(claude_usage_token_expired / _rejected) instead of an opaque collector
error.
For scripts and wrappers
"reserved": trueon an account keeps it tracked and on the dashboard
but excluded from all routing — never picked, never a rotation or handoff
target. For a login that belongs to another workflow.- An exported
CLAUDE_CONFIG_DIR/CODEX_HOMEis honoured as the
initial slot when it names a registered account — your wrapper's routing
decision is consumed, not overridden. HEADROOM_LAUNCH_MARKER— a launch-commit handshake written
immediately before the CLI spawns, so wrappers can distinguish "the CLI
never started" from a real exit without racing anything. Strict no-clobber
install: the marker path must be a fresh file, and an env-controlled path
can never overwrite anything that exists.
Community
- @alexknowshtml fixed identity binding for personal Max accounts with a
nullorgId(#1). - @hanamizuki aligned the dashboard with the widget's semantics for current
windows on limited accounts (#6) — including a same-day review turnaround. - The 429 report above came from X. Keep them coming — real-fleet bug
reports have driven most of this release.
439 tests. The batch went through two cross-model adversarial review rounds
before release; every P1 was fixed in-tree. Update: git pull — the menubar
app is the only component that needs a local build.