[F005,F006] grok auth: directory mount + atomic-write fallback

F005: the single-file bind mount of auth.json pinned the inode, so the
orchestrator's atomic refresh (tmp+rename within ~/.grok) never reached a
running grok container — a long-lived container hung at the login prompt
when the original ~6h token expired. Mount the host ~/.grok DIRECTORY (ro)
at /home/agent/.grok-auth-ro; the entrypoint symlinks ~/.grok/auth.json at
that RO mount so grok + the --check backstop read the live credential (the
directory mount sees the host-side rename) while grok's writable state
(config.toml, sessions/) stays in the image's ~/.grok.

F006: a rotated refresh_token is single-use — xAI invalidates the old one
the instant it issues the new one. If the atomic write failed after the
rotation, the file kept the now-dead old refresh_token and the credential
was permanently lost on the next refresh. _atomic_write now falls back to a
direct write when tmp+replace fails, so the rotated token always lands on
disk (losing the write is catastrophic; losing atomicity is not).

TDD: RED tests watched fail, then GREEN. ruff+mypy clean; 32 grok tests
green, no regressions.
This commit is contained in:
Renn F
2026-06-28 10:10:17 +02:00
parent 812bf1195a
commit 51990d7cad
6 changed files with 109 additions and 26 deletions
+16 -6
View File
@@ -27,12 +27,22 @@ if ! ( cd /app && python -m roboco.agent_sdk.prompt_guard "${ROBOCO_INITIAL_PROM
exit 1
fi
# Auth fail-fast guard. The SuperGrok token (~/.grok/auth.json, mounted read-only)
# has a ~6h TTL; on an expired/missing token headless grok does NOT refresh — it
# hangs forever at an interactive "Waiting for authorization..." prompt, which
# reads as a silent zombie container. The orchestrator refreshes the host token
# on a loop; this is the in-container backstop: exit 78 (EX_CONFIG) immediately
# so _handle_stopped_container surfaces it, instead of hanging for hours.
# Auth fail-fast guard. The SuperGrok token (~/.grok/auth.json) has a ~6h TTL;
# on an expired/missing token headless grok does NOT refresh — it hangs forever
# at an interactive "Waiting for authorization..." prompt, which reads as a
# silent zombie container. The orchestrator refreshes the host token on a loop;
# this is the in-container backstop: exit 78 (EX_CONFIG) immediately so
# _handle_stopped_container surfaces it, instead of hanging for hours.
#
# F005: the orchestrator mounts the host ~/.grok DIRECTORY read-only at
# /home/agent/.grok-auth-ro (a single-file bind mount pins the inode, so the
# atomic auth.json refresh never reached a running container). Symlink
# ~/.grok/auth.json at that RO mount so grok + the --check backstop read the
# LIVE credential (the directory mount sees the host-side rename), while grok's
# own writable state (config.toml, sessions/) still lands in the image's
# ~/.grok. `rm -f` first in case the image baked a stub auth.json.
rm -f /home/agent/.grok/auth.json
ln -s /home/agent/.grok-auth-ro/auth.json /home/agent/.grok/auth.json
if ! ( cd /app && python -m roboco.llm.providers.grok_auth --check ); then
echo "[grok] auth token missing or expired — refusing to run (would hang at" \
"the login prompt). Refresh ~/.grok/auth.json (orchestrator auto-refresh or" \