feat(xai): route OAuth login through the device-code flow#97249
Conversation
Always use the device-code flow for xAI OAuth (no localhost callback) and drop the separate device-code auth method/choice: the single xai-oauth method now runs device code. Add retry + Cloudflare-challenge handling to the token exchange and refresh, migrate stale /oauth/token endpoints on refresh, and drop the now-unused authorization endpoint from discovery.
xAI rotates refresh tokens, so retrying a refresh_token grant after a lost response could resend a token xAI already consumed and burn it, forcing a re-login. Transport errors now fail fast; Cloudflare/5xx/429 responses are still retried on the response path, where the refresh token was provably not consumed.
|
Codex review: needs real behavior proof before merge. Reviewed June 27, 2026, 11:59 AM ET / 15:59 UTC. Summary PR surface: Source -51, Tests +18, Docs -8. Total -41 across 6 files. Reproducibility: yes. for the underlying remote-login mismatch from source: current main's xAI Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Merge only after redacted real device-code login proof is available, the PR body matches the latest alias/retry behavior, and maintainers explicitly accept xAI OAuth moving from loopback callback to device-code by default. Do we have a high-confidence way to reproduce the issue? Yes for the underlying remote-login mismatch from source: current main's xAI Is this the best way to solve the issue? Mostly yes on owner boundary: xAI OAuth behavior belongs in the bundled xAI plugin, and the latest head preserves the shipped aliases. It is not merge-ready until live proof is inspectable, the PR body is corrected, and maintainers accept the auth-provider compatibility change. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against f857e8d66eb3. Label changesLabel justifications:
Evidence reviewedPR surface: Source -51, Tests +18, Docs -8. Total -41 across 6 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
fuller-stack-dev
left a comment
There was a problem hiding this comment.
Maintainer review for 1ade01850f68c78ffa0a75689f794b4e25f62cc6.
Findings:
- [P1] Preserve the shipped xAI
device-codeauth method id before merging.src/cli/models-cli.tsstill mapsopenclaw models auth login --provider xai --device-codetomethod: "device-code", andsrc/commands/models/auth.tsresolves that explicit method throughpickAuthMethod. This PR removes the xAIdevice-codeauth method fromextensions/xai/index.ts, leaving the provider with onlyapi-keyandoauth; a focused probe at this head returned{"methods":["api-key","oauth"],"deviceCodeMatch":null,"oauthMatch":"oauth"}. Since the--device-codeCLI path andxai-device-codeonboarding choice shipped in releases, this breaks existing scripts/docs copied by users. Keep the new single visible OAuth behavior if desired, but preserve a deprecateddevice-codemethod alias that runs the same device-code login and adddeprecatedChoiceIds: ["xai-device-code"]to thexai-oauthmanifest choice so non-interactive onboarding gets a guided replacement instead of an unresolvable choice.
Best-fix judgment: routing xAI OAuth through device code is the right ownership boundary for headless/remote login, but the current patch is not the best merge shape because it deletes a shipped auth contract instead of deprecating it. Please preserve the shipped aliases and add focused regression tests for both --device-code and --auth-choice xai-device-code.
Proof I ran while reviewing:
git diff --check upstream/main...HEADnode scripts/run-vitest.mjs extensions/xai/index.test.ts extensions/xai/xai-oauth.test.tsnode scripts/run-vitest.mjs src/cli/models-cli.test.ts -t 'maps --device-code'node scripts/run-vitest.mjs src/commands/auth-choice.test.ts -t 'removed provider auth choice'- Focused provider-auth probe confirming
device-codeno longer resolves at this PR head
|
Maintainer takeover update for exact head Fixes applied on top of the contributor branch:
Verification on this head:
Known proof boundary: the final automated live smoke stops after xAI issues the device code. It does not complete a human account approval/token exchange in this maintainer run. |
Route xAI OAuth through device-code sign-in so remote and headless hosts do not need a localhost callback. Preserve the legacy manual `xai-device-code` auth choice/method as a compatibility alias to the same device-code flow. Also migrate stale xAI token endpoints on refresh and fail fast on structured refresh errors while keeping retries scoped to detected HTML/Cloudflare challenge responses. Verification: - `node scripts/run-vitest.mjs extensions/xai/index.test.ts extensions/xai/xai-oauth.test.ts` - `node scripts/run-vitest.mjs src/cli/models-cli.test.ts -t 'maps --device-code'` - `node scripts/run-vitest.mjs src/commands/auth-choice.test.ts -t 'removed provider auth choice'` - Crabbox local-container live smoke on exact head `fef3cb24afb01cd1f69cf04ef67ed11d71dfadb3`: xAI discovery and device authorization returned 200. - `$autoreview` after the live smoke: clean. Co-authored-by: Jaaneek <[email protected]> Co-authored-by: fuller-stack-dev <[email protected]>
Route xAI OAuth through device-code sign-in so remote and headless hosts do not need a localhost callback. Preserve the legacy manual `xai-device-code` auth choice/method as a compatibility alias to the same device-code flow. Also migrate stale xAI token endpoints on refresh and fail fast on structured refresh errors while keeping retries scoped to detected HTML/Cloudflare challenge responses. Verification: - `node scripts/run-vitest.mjs extensions/xai/index.test.ts extensions/xai/xai-oauth.test.ts` - `node scripts/run-vitest.mjs src/cli/models-cli.test.ts -t 'maps --device-code'` - `node scripts/run-vitest.mjs src/commands/auth-choice.test.ts -t 'removed provider auth choice'` - Crabbox local-container live smoke on exact head `fef3cb24afb01cd1f69cf04ef67ed11d71dfadb3`: xAI discovery and device authorization returned 200. - `$autoreview` after the live smoke: clean. Co-authored-by: Jaaneek <[email protected]> Co-authored-by: fuller-stack-dev <[email protected]>
Route xAI OAuth through device-code sign-in so remote and headless hosts do not need a localhost callback. Preserve the legacy manual `xai-device-code` auth choice/method as a compatibility alias to the same device-code flow. Also migrate stale xAI token endpoints on refresh and fail fast on structured refresh errors while keeping retries scoped to detected HTML/Cloudflare challenge responses. Verification: - `node scripts/run-vitest.mjs extensions/xai/index.test.ts extensions/xai/xai-oauth.test.ts` - `node scripts/run-vitest.mjs src/cli/models-cli.test.ts -t 'maps --device-code'` - `node scripts/run-vitest.mjs src/commands/auth-choice.test.ts -t 'removed provider auth choice'` - Crabbox local-container live smoke on exact head `fef3cb24afb01cd1f69cf04ef67ed11d71dfadb3`: xAI discovery and device authorization returned 200. - `$autoreview` after the live smoke: clean. Co-authored-by: Jaaneek <[email protected]> Co-authored-by: fuller-stack-dev <[email protected]>
Route xAI OAuth through device-code sign-in so remote and headless hosts do not need a localhost callback. Preserve the legacy manual `xai-device-code` auth choice/method as a compatibility alias to the same device-code flow. Also migrate stale xAI token endpoints on refresh and fail fast on structured refresh errors while keeping retries scoped to detected HTML/Cloudflare challenge responses. Verification: - `node scripts/run-vitest.mjs extensions/xai/index.test.ts extensions/xai/xai-oauth.test.ts` - `node scripts/run-vitest.mjs src/cli/models-cli.test.ts -t 'maps --device-code'` - `node scripts/run-vitest.mjs src/commands/auth-choice.test.ts -t 'removed provider auth choice'` - Crabbox local-container live smoke on exact head `fef3cb24afb01cd1f69cf04ef67ed11d71dfadb3`: xAI discovery and device authorization returned 200. - `$autoreview` after the live smoke: clean. Co-authored-by: Jaaneek <[email protected]> Co-authored-by: fuller-stack-dev <[email protected]>
What Problem This Solves
We had a lot of users reporting problems with the xAI OAuth callback. Sign-in relied on a localhost callback (
127.0.0.1:56121), which fails on VPS, SSH, Docker, and other remote or headless setups, and also when Cloudflare sits in front of the callback. Affected users could not complete xAI/Grok OAuth at all.Why This Change Was Made
The single xAI OAuth method (
xai-oauth, methodoauth) now runs the device-code flow: OpenClaw prints a verification URL plus a short code, the user approves in any browser, and the CLI polls for the token, with no localhost callback. The legacy callback implementation and the separatexai-device-codechoice/method are removed so there is one OAuth path, still named "oauth". Token refresh also migrates credentials that still point at the retired/oauth/tokenendpoint, retries transient5xx/429and Cloudflare HTML-challenge responses, and fails fast on transport errors so a rotated refresh token is never resent.User Impact
xAI/Grok OAuth now works from remote hosts (SSH, Docker, VPS) without a localhost callback. The choice is still presented as "xAI OAuth", and existing
--method oauth/--auth-choice xai-oauthflows are unchanged. Credentials still pointing at the retired/oauth/tokenendpoint self-heal on the next refresh.Evidence
pnpm test extensions/xai/xai-oauth.test.ts: 15 tests pass, covering device-code login without a localhost callback, stale-endpoint rediscovery, discovery-failure handling (no stale reuse), Cloudflare retry-then-succeed and retry-exhaustion, terminal-error no-retry, and refresh not being retried on transport errors.pnpm tsgo:extensionsclean;oxfmt --checkclean.openclaw/main(single commit).auth.x.aicompletes with no localhost callback, access and refresh tokens are issued, and the account identity resolves. Note: the interactiveopenclaw models auth logincommand requires a TTY by design, so it is exercised manually rather than in automation.