grok-token-broker
Holds a refreshable xAI session credential so a gateway can serve the Grok Build coding
plan — without touching the credential the operator's grok CLI depends on.
seed copy the CLI credential into the broker's own store (read-once)
probe-rotation MEASURE whether refresh rotates AND invalidates <- gate
token --raw emit a valid access token, refreshing if near expiry
refresh refresh now
status report state, touching nothing
The problem
The coding plan (https://cli-chat-proxy.grok.com/v1, serving grok-4.6, 500k context)
authenticates with an OIDC session token — api_key: null, env_key: null in the CLI's own
model cache — that expires roughly every six hours. LiteLLM and every other gateway here hold
a static credential, so a naive alias works until the session lapses and then fails closed.
⛔ Why the loop is disarmed until measured
https://auth.x.ai/oauth2/token supports the refresh grant. But a refresh may rotate the
refresh token, and many providers invalidate the old one server-side the moment a new one
issues. The grok CLI holds its refresh token in ~/.grok/auth.json.
Not writing to that file is necessary and not sufficient. If xAI rotates-and-invalidates,
a broker refreshing the same credential kills the CLI login anyway, server-side. heid's
groa_http_dispatch.py refuses to refresh at all for exactly this reason — correct, absent an
answer. This broker exists to get the answer, then act on it.
So probe-rotation is a gate, not a diagnostic: refresh and token-near-expiry both refuse
until a verdict exists and says safe.
| verdict | meaning |
|---|---|
non-rotating |
same refresh token returned. Coexistence safe. Armed. |
rotating-old-still-valid |
rotation happens, old token still works. Coexistence safe. Armed. |
rotating-and-invalidating |
the CLI login is already dead. Broker must not share this credential — it needs its own login. Stays disarmed. |
⚠ The probe spends one refresh, and there is no way to ask the question without spending
it. If the answer is the bad one, the CLI is broken at that moment and needs an interactive
grok re-login. Hence the required --i-accept-this-may-end-the-cli-session flag.
⚠⚠ AND THE BLAST RADIUS IS BOTH GRÓA TRANSPORTS, NOT ONE — verified, 2026-09-16. An earlier
draft of this file implied only the HTTP path was exposed. Wrong: heid/scripts/groa_dispatch.py
invokes the grok CLI directly (argv = ["grok", "-p", prompt, ...]), and the CLI authenticates
from the same ~/.grok/auth.json. The bwrap in the process table is the CLI's own Landlock
sandbox, not something Heid wraps around it. So the read-jail transport and the direct-endpoint
transport share one session: one reaches it through the CLI, the other reads the file the CLI
wrote. An invalidating probe takes Gróa down on every transport until a human re-logs in — not
"the HTTP path degrades". Caught by heid, confirmed here against their source.
Current state — ⛔ SHELVED, deliberately, 2026-09-16
Seeded, committed, disarmed, and with no consumer. Do NOT arm probe-rotation. This is a
finished resting place, not a half-built tool — the gate is working exactly as designed and the
thing it was gating for went away.
Operator ruling the same day, relayed by heid: "keep the jail stop the a/b." Heid dispatches
Gróa through the read jail; groa_http_dispatch.py is kept as a documented fallback with no
scheduled use. Nothing in the fleet is asking for a renewable xAI session.
So the trade inverted while the tool was being built:
the RISK did not shrink -- an invalidating probe still reaches BOTH Gróa transports through
the shared ~/.grok/auth.json session, taking the arm down until an interactive re-login
the PAYOFF went to zero -- it buys token renewal on a transport nobody dispatches
⭐ If something later needs a renewable xAI session, the argument reopens on its own merits and this is sitting here ready. That is the whole reason it was left seeded rather than torn out. Re-read the probe warning below before arming it; none of that risk expired.
Scope openid profile email offline_access grok-cli:access api:access, client b1a00492-….
Probe verdict: UNMEASURED, and correctly so.
⚠ One question this never answered, and it is a billing one: the jail reaches the coding
plan already paid for; the HTTP path reaches the metered API, whose responses carry a
cost_in_usd_ticks field. Whether that bills on top of the plan was never measured and was
not part of the ruling. It is one look at the xAI billing console, which this fleet holds no
credential for.
For a consumer
TOKEN=$(broker.py token --raw)
curl https://cli-chat-proxy.grok.com/v1/... -H "Authorization: Bearer $TOKEN"
⚠ The coding plan speaks the Responses API (api_backend: "responses"), not
/chat/completions. That is a second, independent obstacle to a LiteLLM alias and this
broker does not solve it — it solves the credential half only.
Invariants
- Never writes
~/.grok/auth.json. Reads it once, onseed. - Never prints a token except under
token --raw. - Never logs a token value — expiry, scope and subject only.
- Store is
~/.config/grok-token-broker/at 0700, credential at 0600.