Skip to content

fix(grok): persist rotated OAuth refresh token so GROK_CREDENTIALS survives past 6h#773

Merged
aaronjmars merged 1 commit into
mainfrom
fix/grok-oauth-durable-refresh
Jul 24, 2026
Merged

fix(grok): persist rotated OAuth refresh token so GROK_CREDENTIALS survives past 6h#773
aaronjmars merged 1 commit into
mainfrom
fix/grok-oauth-durable-refresh

Conversation

@aaronjmars

Copy link
Copy Markdown
Collaborator

What

Makes the grok harness's captured X-account OAuth session (GROK_CREDENTIALS) durable past its 6h access-token lifetime.

Why

The captured GROK_CREDENTIALS holds a 6h access token plus a refresh token. xAI rotates and revokes the refresh token on every refresh (confirmed live - auth.x.ai returns invalid_grant "Refresh token has been revoked"). So a static secret self-destructs ~6h after Connect: the first post-expiry run refreshes and rotates the token, but grok writes the new one only to the ephemeral runner $HOME; the secret still holds the now-revoked token, and every later headless run fails Not signed in.

How

Mirrors the existing scripts/mcp-oauth-refresh.sh durable-refresh contract.

  • scripts/run-grok.sh (§2b) refreshes the access token from the refresh token before each run, rewrites auth.json, and persists the rotated file back to the GROK_CREDENTIALS secret via a secrets-write PAT. Reuses the existing MCP_SECRETS_PAT (or GH_GLOBAL) - the default GITHUB_TOKEN cannot write secrets. Refreshes only within GROK_OAUTH_SKEW seconds of expiry (default 1800). No-op on the XAI_API_KEY path and on a local grok login session.
  • aeon.yml: adds a grok) case to the harness AUTH_MODE detection - grok was defaulting to openrouter, so a Connected X account was mislabelled in the banner; now native-oauth. Passes MCP_SECRETS_PAT/GH_GLOBAL/GROK_OAUTH_SKEW into the grok install step, and GROK_CREDENTIALS/XAI_API_KEY into the resolve step so the new case can see them.
  • messages.yml: same grok env on the inbound-message run.
  • dashboard GrokAuthModal: a secrets-PAT setup note (parallels McpPanel), hidden once MCP_SECRETS_PAT/GH_GLOBAL is set.
  • docs/harnesses.md + CHANGELOG: document the durable-refresh contract.
  • scripts/tests/test_run_grok.sh: 6 integration tests (refresh+persist, no-PAT warn, skip-when-valid, invalid_grant, API-key path).

Notes

  • This reuses the existing MCP_SECRETS_PAT secret rather than introducing a new name. A follow-up PR standardizes both the MCP and grok paths onto a single GH_SECRETS_PAT (with GH_GLOBAL fallback).
  • Concurrent grok runs that each hit an expired token still race (each refresh revokes the other's token); for high parallelism, refresh centrally on a schedule so exactly one run mints per interval. Documented in docs/harnesses.md.

Test

  • bash scripts/tests/test_run_grok.sh -> ALL PASS (incl. the 6 new OAuth tests)
  • actionlint clean on both workflows
  • Verified end-to-end on a live instance: a forced run logged grok oauth: persisted rotated GROK_CREDENTIALS (durable refresh active) and the secret's updated_at bumped.

…rvives past 6h

The captured GROK_CREDENTIALS holds a 6h access token plus a refresh token that
xAI rotates AND revokes on every refresh, so a static secret self-destructs ~6h
after Connect: the first post-expiry run refreshes and rotates the token but writes
the new one only to the ephemeral runner HOME, leaving the secret holding a
now-revoked token. Headless runs then fail "Not signed in".

Fix (mirrors scripts/mcp-oauth-refresh.sh's durable-refresh contract):

- scripts/run-grok.sh section 2b refreshes the access token from the refresh token
  before each run, rewrites auth.json, and PERSISTS the rotated file back to the
  GROK_CREDENTIALS secret via a secrets-write PAT. Reuses the existing
  MCP_SECRETS_PAT (or GH_GLOBAL); the default GITHUB_TOKEN cannot write secrets.
  Refreshes only within GROK_OAUTH_SKEW seconds of expiry (default 1800). No-op on
  the XAI_API_KEY path and on a local `grok login` session.
- aeon.yml: add a grok) case to the harness AUTH_MODE detection (grok was
  defaulting to openrouter, mislabelling a Connected X account as native), and pass
  MCP_SECRETS_PAT/GH_GLOBAL/GROK_OAUTH_SKEW into the grok install step.
- messages.yml: same grok env on the inbound-message run.
- dashboard GrokAuthModal: a secrets-PAT setup note (parallels McpPanel), hidden
  once MCP_SECRETS_PAT/GH_GLOBAL is set.
- docs/harnesses.md + CHANGELOG: document the durable-refresh contract.
- scripts/tests/test_run_grok.sh: 6 integration tests (refresh+persist, no-PAT
  warn, skip-when-valid, invalid_grant, API-key path).

Verified end-to-end on a live instance: a forced run logged "persisted rotated
GROK_CREDENTIALS (durable refresh active)" and the secret's updated_at bumped, so
auth survives past 6h.
@aaronjmars
aaronjmars merged commit dcd3cb5 into main Jul 24, 2026
6 checks passed
@aaronjmars
aaronjmars deleted the fix/grok-oauth-durable-refresh branch July 24, 2026 18:37
aaronjmars added a commit that referenced this pull request Jul 24, 2026
… for MCP + Grok (#774)

One secrets-write PAT now serves both OAuth refresh-persistence paths:
- MCP OAuth (scripts/mcp-oauth-refresh.sh), and
- Grok X-account OAuth (scripts/run-grok.sh section 2b, landed in #773).

Both read GH_SECRETS_PAT and fall back to GH_GLOBAL. #773 reused the MCP-named PAT
as an interim; this generalizes the name now that it drives two features.
MCP_SECRETS_PAT was still under [Unreleased], so no tagged release ever shipped it:
operators tracking main who already set it should re-add the PAT as GH_SECRETS_PAT;
GH_GLOBAL users are unaffected.

- scripts/run-grok.sh, scripts/mcp-oauth-refresh.sh: read GH_SECRETS_PAT // GH_GLOBAL
- aeon.yml / messages.yml: grok install/run env now passes GH_SECRETS_PAT
- dashboard: secrets-catalog entry + McpPanel + GrokAuthModal point at GH_SECRETS_PAT;
  the catalog description + secrets reference now name both MCP and the Grok harness
- docs (harnesses, mcp-oauth, CONFIGURATION) + .claude skill references
- CHANGELOG: rename note under Changed
- tests updated; test_run_grok.sh + test_mcp_oauth_refresh.sh both green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant