Releases: domanski-ai/headroom
Release list
v0.4.0 — the menu-bar app
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.
v0.3.0 — widgets
headroom v0.3.0 — widgets
Requested on X ("kinda wish it had a dashboard widget, perhaps even a taskbar
widget"). Here they are. One data plane, glanceable everywhere, never lying about
freshness.
Menu-bar widget (macOS, SwiftBar)
A one-line fleet readout in your menu bar: hr 3/4 · 97% (accounts with proven
headroom / total, and the fullest current 5-hour tank). The dropdown lists every
account with both windows and reset times. Drop
integrations/swiftbar/headroom.1m.sh into SwiftBar and point it at a local
headroom serve or an SSH-forwarded one.
The client is deliberately dumb: it fetches server-rendered text, validates a
version sentinel, strictly allowlists every line (fixed colours, one refresh, one
loopback dashboard link), and prints. It never executes fetched content.
Compact dashboard
http://127.0.0.1:8377/widget (or ?compact=1) is the full themed dashboard sized
as a pinnable mini app — headline fleet bars plus slim per-account rows. Pin it with
Safari's Add to Dock, Edge's install-as-app, or Übersicht.
Windows tray (experimental)
experimental/windows/headroom-tray.ps1 — a PowerShell 5.1 system-tray applet
(nothing to install) that polls a forwarded headroom serve and shows a four-state
battery icon. Marked experimental until a real Windows end-to-end run confirms it;
reports welcome.
Honesty guarantees
Widgets share one projection with the dashboard: stale or held data always renders
gray with no live number, freshness thresholds match everywhere, and the public feed
carries no routing or eligibility claims. Everything is loopback-only; remote access
is SSH forwarding, documented, and the only supported path.
Serving three feeds (/usage.json, /widget.json, /widget.txt) shares one
single-flight refresh gate, so a 60-second widget poll never amplifies provider
reads.
v0.2.1 — automatic handoff, on by default
headroom v0.2.1 — automatic handoff, on by default
The headline: headroom claude can now continue a capped conversation on a fresh
account by itself, in the same terminal, with no commands to type. This is the thing
headroom was built for — never babysit a capped session again.
Automatic handoff (on by default)
Launch your session with headroom claude as usual — that's it. When that exact session hits a subscription
cap, headroom:
- Confirms the cap with three independent proofs — a
StopFailurehook matched as
rate_limit, a narrow session/weekly-limit message, and a fresh identity-bound
usage read showing the account is actually capped. Missing or ambiguous evidence
leaves your session running, untouched. - Derives the model you were actually on (from the transcript, so an in-session
/modelswitch is respected), and picks a different account with proven headroom
for that same model family. - Sends Claude one
SIGTERM, waits for its own clean shutdown, copies the conversation
into the target account (byte-for-byte; the source is never modified), and relaunches
claude --resume … --fork-sessionin the same terminal.
Your session pauses for a few seconds and continues on the fresh account. What carries:
the conversation, model-family routing, and the working directory. What does not:
background tasks, live MCP connections, pending permission approvals, and permission
mode. Three automatic handoffs in any ten minutes trips a loop guard.
It is on by default, because uninterrupted continuation is the point. Every step
is fail-closed — any doubt and headroom leaves your session alone — and
routing.auto_handoff: false (or --headroom-no-auto-handoff per run) turns it off.
Also in this release
headroom handoffis now fully transactional: a global lock, no-clobber publication,
and crash-recoverable staging. New flags:--yes(confirmed non-interactive handoff)
and--model FAMILY(when the session model can't be inferred). Manual handoff of an
interrupted tool call requires--force.- Model-family–aware routing everywhere: a session capped on one model is never handed
to an account whose scoped weekly cap for that model is exhausted.
Platform note
The automatic-handoff path is proven end-to-end on Linux (Claude Code 2.1.x): a real
cap was detected, the session was stopped, moved, and resumed on another account with
full history intact. The one-SIGTERM stop contract has not yet been validated on
macOS; see docs/KNOWN-LIMITS.md. Usage tracking, the dashboard, manual handoff, and
rotation are unaffected and work on both platforms.
v0.1.2 — session handoff + multi-account macOS
v0.1.2 — hand off a capped session; multiple Claude accounts on one Mac
Two headline features. Update: git pull — no other steps.
🖐️ Session handoff (EXPERIMENTAL)
Hit your 5-hour cap mid-conversation? /exit, then:
headroom handoffYour conversation continues on your fullest other account, from the same directory, with full history — the capped account cools until its window resets. Under the hood: the transcript is verified line-by-line, copied hash-checked into the target account's home, and resumed with claude --resume <id> --fork-session (a fresh session id, so histories never collide). Every handoff prints a baton — what moved, what doesn't carry (background tasks, MCP connections, per-session permission approvals), and the exact resume command — and is recorded in a ledger that refuses accidental double-handoffs. The source transcript is never modified. Use --print to stage without launching, --to <slot> to pick the target. The status line tells you when: capped -> /exit, then: headroom handoff.
Nine fail-closed guards (still-writing detection, mid-tool-call refusal, router-validated target, atomic hash-verified copy, no overwrites, symlink rejection, cross-domain data-boundary warning, and more). Proven end-to-end on real accounts before release. Same machine, Claude, file-isolated accounts for v1.
🍎 Multiple Claude accounts on one Mac
Current Claude Code builds keep a separate macOS Keychain item per config directory (verified against the official 2.1.207 binary) — so multiple isolated Claude accounts on one Mac work natively. headroom now reads the per-account namespaced item, and headroom connect probes for the capability: with a current CLI, extra Claude accounts connect normally; on older builds that still share one Keychain item, the connect is refused up front (that shared item is how a second login used to silently break the first). If you were told "one Claude account per Mac" in v0.1.1 — update Claude Code, pull this, and connect away. Real-world confirmations welcome.
Also
- Routing now honours
reserve_percenton the model-scoped weekly caps as well. - Statusline persists a small session journal (id, transcript path, cwd — throttled, local, 0600) so handoff can identify your session without guessing.
81 tests green. Feedback on the handoff experiment very welcome — it's the feature the rest of this project existed to make possible.
v0.1.1 — macOS fixed
v0.1.1 — macOS fixed, smarter rotation
If v0.1.0 was broken for you on macOS, this release is the fix. Update: git pull (or re-clone) — no other steps.
macOS Keychain support (the big one)
- Recent Claude Code on macOS stores its OAuth token in the login Keychain, not
~/.claude/.credentials.json— v0.1.0 couldn't read it, so every Mac Claude account was held as credentials missing. headroom now reads the Keychain item directly (approve thesecurityprompt once — Always Allow). One normalclaudelogin on your Mac just works. - Connect guard: on macOS all Claude logins share ONE Keychain item — a second
claudelogin overwrites the first (this is what caused theREFUSED … already connectedloop some users hit).headroom connectnow refuses that second login up front, before it can break your existing slot, and explains the platform limit: one Claude account per Mac. Extra Claude accounts belong on a Linux host; Codex accounts are fully isolated everywhere, including macOS.
Codex tracking robustness
- The Codex app-server reports rate-limit windows by their real duration and omits windows that aren't a current constraint; v0.1.0 assumed a fixed primary=5h/secondary=weekly shape and dropped the whole reading when it didn't hold. Windows are now bucketed by actual duration — Codex readings stay live through resets.
Reserve routing
- New
routing.reserve_percent(the setup wizard asks): skip any account with less than N% headroom left so sessions start fresh instead of hitting a wall mid-task. Default 0 keeps use-to-the-limit behaviour.
Docs
- README: multi-machine FAQ (usage is read per-account provider-side; reads are token-safe and never log out your other sessions), reserve section, corrected macOS quickstart note.
- KNOWN-LIMITS: accurate macOS Keychain section (
CLAUDE_CONFIG_DIRdoes not relocate credentials on macOS — only Linux/Windows).
59 tests green. Thanks to @hanamizuki and the DM bug reporter for the macOS reports. 🙏
v0.1.0 — first public release
headroom tracks the remaining capacity of every Claude and ChatGPT/Codex subscription you own — read live, without spending a single token — puts it on a dashboard you'll actually want to look at, and rotates your tools to the next account with headroom the moment one hits a limit.
Highlights
- Live usage, both providers. Claude via the OAuth usage API; Codex via the Codex app-server (
account/rateLimits/read). Real-time and identity-bound — never a stale guess. - A dashboard worth looking at. Five switchable themes (Midnight, Minimal, Chrome, Paper, Terminal) with an instrument bar of meters, gauges and per-account bars. Try it before connecting anything:
headroom serve --demo. - Fail-closed rotation.
headroom claude/headroom codexlaunch on an account with proven headroom; on a limit-hit,headroom rotatecools it down until its window resets and hands you the next one. - Claude Code integration. A color-coded status line and a
/rotatorskill. - Safe by construction. stdlib-only Python (3.9+), no network beyond the two read endpoints, emails redacted by default, credentials never moved. Adversarially reviewed cross-model before release.
Quickstart
git clone https://github.com/domanski-ai/headroom
cd headroom
./install.sh
headroom serve --demo # preview with sample data, no setup
headroom setup # connect your accountsSee the README for the full command list and docs/HOW-IT-WORKS.md for the architecture. MIT licensed.