feat(grok): auto-refresh the SuperGrok token + fail fast on a dead one

The grok access token has a ~6h server-set TTL (the client cannot lengthen it),
the CLI has no refresh command, and headless 'grok -p' does NOT self-refresh an
expired token -- it hangs forever at an interactive 'Waiting for authorization...'
prompt. Live evidence: a fleet went silent within ~3 min of the token's 06:54
expiry, every agent a zombie hung at the prompt, requiring a manual 'grok login'.

- grok_auth.refresh_if_stale: mint a fresh access token from the offline_access
  refresh token via xAI's OIDC refresh_token grant (https://auth.x.ai/oauth2/token),
  atomically rewriting auth.json. The orchestrator runs it once per dispatch tick
  (serial -> no concurrent refresh-token rotation race; throttled to 60s), keeping
  the host credential live so agents never mount a dead one. No more manual login.
- Entrypoint --check guard: refuse to run (exit 78) on a missing/expired token
  instead of hanging for hours -- surfaced to _handle_stopped_container.
- Orchestrator grok-dir mount flipped read-only -> read-write in all three compose
  files so the refresh can rewrite auth.json; the per-agent file mount stays RO.

Verified: 10 unit tests; the --check guard exits 0/1/1 (valid/expired/missing)
inside the real roboco-agent-grok image. Gate green (ruff/mypy/xenon).
This commit is contained in:
Renn F
2026-06-19 10:15:58 +02:00
parent 5ea2f82f64
commit f2e787c577
8 changed files with 439 additions and 10 deletions
+5 -3
View File
@@ -350,9 +350,11 @@ services:
# SuperGrok auth — mount the host ~/.grok at the SAME host path the
# orchestrator passes to each Grok agent's `-v`, so its auth.json exists()
# check passes here AND the agent bind resolves on the host. One canonical
# var for source AND target (they must be equal in docker-in-docker); `grok
# login` on the host writes auth.json (auto-refreshing). Read-only.
- ${ROBOCO_HOST_GROK_DIR:-/home/renzof/.grok}:${ROBOCO_HOST_GROK_DIR:-/home/renzof/.grok}:ro
# var for source AND target (they must be equal in docker-in-docker).
# Read-WRITE: the orchestrator auto-refreshes the ~6h token in place
# (grok_auth.refresh_if_stale) so agents never mount a dead credential;
# each agent's own auth.json mount stays read-only.
- ${ROBOCO_HOST_GROK_DIR:-/home/renzof/.grok}:${ROBOCO_HOST_GROK_DIR:-/home/renzof/.grok}
# Shared config directory for MCP configs (writable)
- ${ROBOCO_DATA_DIR:-./data}/mcp-configs:/app/mcp-configs
# Generated prompts directory - composed at runtime from layers