feat(auth): add Anthropic OAuth token refresh#8602
feat(auth): add Anthropic OAuth token refresh#8602maxtongwang wants to merge 12 commits intoopenclaw:mainfrom
Conversation
scripts/sync-claude-oauth.ts
Outdated
| import * as fs from "node:fs"; | ||
| import * as path from "node:path"; | ||
|
|
There was a problem hiding this comment.
[P1] process.env.HOME! can be undefined (e.g., Windows, some CI, sandboxed envs), which will throw inside path.join before you can print the friendly error. Consider resolving home via os.homedir() (or guard HOME explicitly) so the script fails with a clear message instead of a crash.
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/sync-claude-oauth.ts
Line: 1:3
Comment:
[P1] `process.env.HOME!` can be undefined (e.g., Windows, some CI, sandboxed envs), which will throw inside `path.join` before you can print the friendly error. Consider resolving home via `os.homedir()` (or guard HOME explicitly) so the script fails with a clear message instead of a crash.
How can I resolve this? If you propose a fix, please make it concise.
scripts/sync-claude-oauth.ts
Outdated
| console.log("Updated agent store (agents/main/agent/auth-profiles.json)"); | ||
| } | ||
|
|
There was a problem hiding this comment.
[P2] new Date(profile.expires).toISOString() will throw RangeError: Invalid time value when expires is 0 (or otherwise invalid), which can happen because the script sets expires: oauth.expiresAt || 0. Either avoid calling toISOString() when expires is falsy, or default to Date.now() + … so logging never crashes.
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/sync-claude-oauth.ts
Line: 67:69
Comment:
[P2] `new Date(profile.expires).toISOString()` will throw `RangeError: Invalid time value` when `expires` is `0` (or otherwise invalid), which can happen because the script sets `expires: oauth.expiresAt || 0`. Either avoid calling `toISOString()` when `expires` is falsy, or default to `Date.now() + …` so logging never crashes.
How can I resolve this? If you propose a fix, please make it concise.
scripts/sync-claude-oauth.sh
Outdated
| #!/bin/bash | ||
| # Sync Claude Code OAuth credentials into OpenClaw auth-profiles.json | ||
| # Usage: ./scripts/sync-claude-oauth.sh |
There was a problem hiding this comment.
[P3] Minor portability: the shebang is #!/bin/bash, which can be missing on some systems (Nix, some containers). If this script is intended to be broadly runnable, #!/usr/bin/env bash is usually more portable.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/sync-claude-oauth.sh
Line: 1:3
Comment:
[P3] Minor portability: the shebang is `#!/bin/bash`, which can be missing on some systems (Nix, some containers). If this script is intended to be broadly runnable, `#!/usr/bin/env bash` is usually more portable.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| const access = data.access_token?.trim(); | ||
| const newRefresh = data.refresh_token?.trim(); |
There was a problem hiding this comment.
[P2] response.text() is included verbatim in the thrown error. If the token endpoint returns structured JSON with sensitive fields, this could leak into logs/telemetry. Consider parsing a small subset (e.g., error, error_description) or truncating the body before embedding it in the error message.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/anthropic-oauth.ts
Line: 60:62
Comment:
[P2] `response.text()` is included verbatim in the thrown error. If the token endpoint returns structured JSON with sensitive fields, this could leak into logs/telemetry. Consider parsing a small subset (e.g., `error`, `error_description`) or truncating the body before embedding it in the error message.
How can I resolve this? If you propose a fix, please make it concise.
Update: Auto-sync Claude Code OAuth + Keychain fallback (e77978d92)This commit fixes the core issue where Anthropic OAuth tokens can't auto-refresh because Claude Code stores credentials in macOS Keychain while OpenClaw was reading from the empty Changes
Verification
|
36abfdb to
b302a7d
Compare
2f1aebb to
705ccbb
Compare
|
@steipete, please look for this solution |
|
2 failed CIs doesn't seem to be related to my changes. @steipete Please check and consider merging this. |
|
@steipete — Could you please review this PR? It's been open for weeks and is blocking all users who rely on Anthropic OAuth. I've confirmed the underlying issue thoroughly on v2026.2.9:
The pi-ai library already has full Anthropic OAuth support ( This PR wires up exactly what's missing. Multiple users have confirmed it works locally. The CI failures on macOS/Windows appear unrelated to the actual code changes (as @maxtongwang noted). This is the #1 most impactful issue on the tracker right now — 15+ comments on #9095, affecting everyone using Anthropic without a paid API key. Would really appreciate a review. 🙏 |
|
@steipete this PR is urgent. please review/approve. thanks! |
868c4bb to
c46c549
Compare
b45360a to
63f54f6
Compare
bfc1ccb to
f92900f
Compare
Urgent: This PR is the only path to Anthropic OAuth support in OpenClawI just tested both auth methods with a fresh OAT token ( OAT tokens cannot be used with the raw Anthropic API. This PR implements the OAuth flow that makes them work. Without it, the only way to use Anthropic in OpenClaw is with a paid API key. This blocks every user who wants to use their Claude Pro/Max subscription with OpenClaw instead of paying separate API credits. With @steipete's transition to OpenAI, could one of the active maintainers review this? The auth subsystem has no dedicated maintainer in CONTRIBUTING.md. cc @sebslight @tyler6204 @gumadeiras — this has been open for weeks with CI passing and confirmed working by 5+ users. No human reviewer has been assigned. |
|
i was one of the first trying to figure this out and at this point i've moved to the OpenAI pro subscription, despite my reservations. Anthropic, especially after today's news, will not make it easy to make this happen. everything they can do to patch they will. to be clear, i wanted xAI to do what OpenAI did today so i'm riding the wave until the next better alternative comes available. |
63f54f6 to
45f40a5
Compare
0d5dfe2 to
23322ca
Compare
279a530 to
9e8feb3
Compare
|
can yall try the current implementation and let me know if you still have problems? |
|
@gumadeiras — tested the current implementation (v2026.2.15). Here's what I found: What WORKSThe
What DOES NOT WORK1. The 2. 3. Manually created 4. OAT tokens don't work with the raw API: Root causeThe OAuth refresh infrastructure exists in v2026.2.15, but there's no way to create an What PR #8602 adds
SuggestionThe simplest fix might be to wire |
|
@nikolasdehor I meant the current implementation in this PR, not on main |
|
@gumadeiras I checked out and built the PR branch locally. Here are my test results: Build
CLI Commands
What I couldn't test without TTYThe actual I'll run this in an interactive terminal session and report back on the full OAuth flow (browser → consent → token → refresh cycle). Code Review ObservationsThe implementation looks solid:
Key improvement over mainOn main (v2026.2.15):
On this PR branch:
This PR would resolve #9095 (my issue), #17873, and #18624. Looking forward to testing the full interactive flow. |
|
sounds good; thanks for testing |
|
@gumadeiras Important finding from TTY testing: My earlier report was incomplete — the "requires interactive TTY" error was masking the real issue. When I run with an actual TTY (via Root cause
What works vs what doesn't
So Suggested fixThe // Current (broken):
const provider = loadedPlugins.find(p => p.id === providerId);
if (!provider) throw new Error(`Unknown provider "${providerId}"`);
// Fixed:
const provider = loadedPlugins.find(p => p.id === providerId)
?? getOAuthProvider(providerId); // fall back to pi-ai registry
if (!provider) throw new Error(`Unknown provider "${providerId}"`);This would connect the existing Test environment
|
|
you don't need TTY on the gateway machine but you need TTY in some machine: Setup-tokens are created by the Claude Code CLI, not the Anthropic Console. You can run this on any machine: Paste the token into OpenClaw (wizard: Anthropic token (paste setup-token)), or run it on the gateway host: If you generated the token on a different machine, paste it using: there is no pi will check if the token is oauth and route things properly |
Add refresh handler for Anthropic OAuth credentials using the Claude
platform token endpoint. This enables OpenClaw to auto-refresh expired
Anthropic OAuth tokens instead of requiring manual re-authentication
or falling back to static API keys/setup-tokens.
Changes:
- New `src/agents/anthropic-oauth.ts`: refresh handler hitting
`platform.claude.com/v1/oauth/token` with standard OAuth2 refresh flow
- Updated `src/agents/auth-profiles/oauth.ts`: added Anthropic to the
provider dispatch chain (before Chutes/Qwen/generic)
- New `scripts/sync-claude-oauth.{sh,ts}`: utility to seed initial OAuth
credentials from Claude Code's credential store
Relates to openclaw#2697 openclaw#6400 openclaw#8223 openclaw#8226 openclaw#8405
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Use os.homedir() instead of process.env.HOME (P1) - Guard Date display when expires is 0/falsy (P2) - Parse error response JSON instead of leaking raw body (P2) - Use #!/usr/bin/env bash for portability (P3) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Replace `as any` casts with proper ProfileStore type. Auto-format with oxfmt. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add Claude Code to the external CLI credential sync chain (alongside Qwen/MiniMax) so tokens are refreshed from Keychain on store load. When Anthropic token refresh fails, fall back to reading fresh credentials from Claude Code's Keychain. Write refreshed tokens back to the Keychain after successful refresh to keep both in sync. Update sync-claude-oauth.ts to use readClaudeCliCredentials() (Keychain-first) instead of raw file reads. Add unit tests for refreshAnthropicTokens. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
9e8feb3 to
9fa5e77
Compare
- Add `refreshAnthropicTokens` to refresh expired Anthropic OAuth tokens using the Claude platform endpoint, mirroring Claude Code CLI's own refresh flow. Includes Keychain fallback: if refresh fails, reads fresh credentials from Claude Code CLI before throwing. - Auto-sync Claude Code CLI OAuth credentials into `anthropic:default` during external CLI sync, but only when the existing profile is already OAuth — prevents api_key/token profiles from being auto-converted. - Extract shared `coerceExpiresAt` helper to `src/agents/oauth-utils.ts`, eliminating duplication between Anthropic and Chutes OAuth modules. - Fix `syncExternalCliCredentials`: migrate Qwen sync block to use shared `syncExternalCliCredentialsForProvider` helper, eliminating a double-read bug and ~20 lines of duplicate logic. - Add optional `deps` parameter to `syncExternalCliCredentials` for testable credential-reader injection (replaces broken vi.mock ESM pattern). - Remove redundant `String(cred.provider)` wrappers in `oauth.ts`. - Fix unsafe cast in `anthropic-oauth.test.ts`: use `result.email` directly. Supersedes openclaw#8602. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
|
Hey, we were pulling this into our fork and ran it through our @claude automated review workflow. The workflow kept catching issues and iterating until clean — ended up with a handful of real fixes. Opened #21518 as a superseding PR with everything included. Here's what we found: Bug — Qwen double-read in Bug — Code quality — redundant Test quality — unsafe cast in Refactor — PR #21518 has all of the above in a single commit on top of a clean branch from main. Feel free to pull the changes into this branch or close this one in favour of that. cc @gumadeiras for review |
Summary
Adds automatic OAuth token refresh for Anthropic credentials, enabling OpenClaw to use Claude Code's OAuth tokens with auto-renewal. Currently Anthropic is the only major provider in the OAuth dispatch chain without a refresh handler — tokens expire and fall back to static API keys or require manual re-auth.
src/agents/anthropic-oauth.ts— refresh handler usingplatform.claude.com/v1/oauth/token(same endpoint Claude Code CLI uses), follows the existing Chutes/Qwen patternsrc/agents/auth-profiles/oauth.tsscripts/sync-claude-oauth.{sh,ts}to seed initial OAuth credentials from Claude CodeRelated Issues
Test plan
pnpm build)pnpm check— noany, oxfmt clean)refreshAnthropicTokens()returns fresh access tokenanthropic-oauth.ts(refresh success, missing refresh token, error responses)🤖 Generated with Claude Code