Headroom, Round Two: Four Walls Between Me and a Working Token Saver
Three days ago I wrote an autopsy about ripping a dead Headroom integration out of my 9router fork. The endpoint I was calling didn’t exist in the version I was running, the failure was swallowed silently, and the cleanest fix was deletion.
This is the follow-up. Headroom is now actually running behind my dashboard, compressing tokens. Getting there took one afternoon and four separate walls — and after last time, I expected the bug to be another version mismatch. It wasn’t. Not once.
The Setup
Instead of re-integrating Headroom into my fork’s custom code, I switched to a branch tracking upstream 9router v0.5.50 (that’s the router’s version — not to be confused with the Headroom v0.5.4 from the autopsy), which ships a full integration: Python detection (lib/headroom/detect.js), process lifecycle management (lib/headroom/process.js), management routes (/api/headroom/start, /stop, /status), and an embedded proxy that serves Headroom’s own dashboard inside 9router’s UI.
Exactly what the autopsy said the integration should have been. Infrastructure management, not a feature flag.
I flipped the toggle. It didn’t work. Of course it didn’t.
Wall 1: The Invisible Install
I had installed Headroom via pipx. The dashboard insisted it wasn’t installed.
The detection logic probes a list of Python interpreter candidates — Homebrew paths, framework installs, user-local paths — and runs pip show headroom-ai against each one. It needs the package visible to a probed interpreter; a headroom binary sitting on PATH isn’t enough, because the dashboard also uses that interpreter to manage the proxy. pipx installs into its own isolated venv, so every candidate interpreter answered “no such package.” The install was real; it was just invisible to every Python the dashboard could see.
Uninstalled from pipx. Cleared the stale PID and log files left in ~/.9router/headroom/ from my earlier manual-proxy experiments. Started over.
Wall 2: The PEP 668 Wall
The dashboard has an install button. It runs pip install headroom-ai[proxy] for you. On a modern Homebrew-managed macOS Python, that fails instantly:
1
error: externally-managed-environment
PEP 668 marks the whole environment externally managed — pip refuses to install into it at all, even with --user. That’s the protection that makes the “just click install” button a dead end on this setup.
The workaround that made detection happy:
1
/usr/local/bin/python3.12 -m pip install --user --break-system-packages "headroom-ai[proxy]"
--break-system-packages is the actual override — a deliberate footgun flag, used here with eyes open — while --user just steers the install into the user site of an interpreter the detection logic actually probes. That lands headroom-ai 0.34.0 exactly where the dashboard looks.
Wall 3: The Binary Nobody Could Find
Installed, detected — but the headroom binary itself lands in ~/Library/Python/3.12/bin, which is not on the dashboard’s extended PATH. A symlink into ~/.local/bin — which is — closed the loop.
Dashboard now reports: Headroom running, v0.34.0. Three walls down.
Wall 4: The Broken Embedded Dashboard
Headroom ships its own dashboard, and 9router proxies it at /api/headroom/proxy/dashboard. Mine rendered as unstyled text soup.
The proxy rewrites Headroom’s HTML so its URLs resolve through the Next.js app — but only fetch(' calls, and only on the dashboard root page, not sub-pages. Headroom 0.34.0’s HTML loads Tailwind, htmx, and Alpine with root-relative script tags, and links its settings page the same way:
1
2
<script src="/dashboard/static/tailwind.min.js"></script>
<a href="/dashboard/settings">
Every one of those 404’d against the Next.js app — no Tailwind, hence the text soup. The fix was a second rewrite pass for src/href attributes, prefixing them with the proxy mount point (DASHBOARD_PREFIX = "/api/headroom/proxy"), plus applying the rewrite to sub-pages too:
1
.replace(/(src|href)="(?=\/dashboard(?:\/|"))/g, `$1="${DASHBOARD_PREFIX}`)
Styled dashboard. Then I clicked into Settings and got Failed to load settings: HTTP 404 — the settings page fetches /settings, /settings/schema, and /settings/apply, and the fetch-rewrite allowlist didn’t include settings. One allowlist entry later, the whole embedded UI worked.
Both dashboard fixes are upstream now as PR #3065, alongside a combos import/export port from my fork.
The Receipts
Last time the lesson was “check if it’s actually running.” So this time, proof. After one working session routed through the proxy:
- 21 requests compressed, zero failed
- 69,627 tokens stripped out of 622,745 — 11.2% average
- Best single request: 10,798 → 8,559 tokens (20.7%)
- ~$0.56 saved on the session, per Headroom’s own cost accounting
Not life-changing money. But it’s real, it’s measured, and it’s per-request visible in a dashboard that now actually renders.
What Round Two Taught Me
The autopsy’s lesson was check the version before assuming the API exists. Round two’s lesson is subtler: the hard part of shipping a sidecar isn’t the sidecar — it’s the environment detection around it.
Every wall lived in the seams:
- Package manager isolation (pipx) defeating interpreter probing
- OS-level packaging policy (PEP 668) defeating the one-click installer
- PATH assumptions defeating binary discovery
- Root-relative URLs defeating the reverse proxy
The first three aren’t code bugs — Headroom, 9router, pipx, and PEP 668 are each doing their job; the failure lives where they meet. (Arguably 9router could smooth those seams — pipx awareness, a PEP 668 fallback in the installer — but that’s a different PR.) The fourth was squarely a 9router proxy bug, which is why that one went upstream. All four only surface on a real machine with a real Python mess, which is to say: everyone’s machine.
The token saver is on. The dashboard renders. And this time, when a request goes through, there’s an actual number to prove it.