# 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 — the same remedy heid's exit-3 path already names. **Run it when a broken CLI login is a two-minute annoyance, not mid-panel.** Hence the required `--i-accept-this-may-end-the-cli-session` flag. ## Current state Seeded on **nh3-dev** 2026-09-16. Scope `openid profile email offline_access grok-cli:access api:access`, client `b1a00492-…`. **Probe NOT yet run** — it is the operator's call when to spend it. ## For a consumer ```bash 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, on `seed`. - 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.