Skip to content

fix(auth): add Claude CLI to EXTERNAL_CLI_SYNC_PROVIDERS for automatic OAuth token sync#70902

Merged
obviyus merged 1 commit into
openclaw:mainfrom
starvex:fix/claude-cli-external-sync
Apr 25, 2026
Merged

fix(auth): add Claude CLI to EXTERNAL_CLI_SYNC_PROVIDERS for automatic OAuth token sync#70902
obviyus merged 1 commit into
openclaw:mainfrom
starvex:fix/claude-cli-external-sync

Conversation

@starvex

@starvex starvex commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Problem

readClaudeCliCredentialsCached already exists and reads OAuth credentials from:

  • macOS Keychain (Claude Code-credentials service)
  • ~/.claude/.credentials.json (fallback)

However, it is not registered in EXTERNAL_CLI_SYNC_PROVIDERS. This means the auth store's cached copy of the Claude CLI OAuth token goes stale when Keychain tokens rotate (~8h cycle), causing a silent fallback to paid Anthropic API keys.

Symptoms

  • openclaw doctor reports anthropic:claude-cli as expiring or expired
  • Gateway logs show model fallback decision: candidate_failed ... reason=timeout next=anthropic-api/...
  • API usage appears on platform.claude.com despite primary being set to claude-cli/*
  • Workaround: manual openclaw models auth login --provider anthropic --method cli every ~8h

Fix

Add a claude-cli entry to EXTERNAL_CLI_SYNC_PROVIDERS, mirroring the existing minimax-portal pattern. Uses the same 900s TTL cache.

Diff

+  {
+    profileId: CLAUDE_CLI_PROFILE_ID,
+    provider: "claude-cli",
+    readCredentials: () => readClaudeCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS }),
+  },

1 file changed, 7 insertions, 2 deletions.

Testing

  • Verified monkey-patched dist on production gateway (macOS, Claude Max subscription)
  • After patch: store auto-syncs from Keychain, no more silent fallback to API keys
  • CLAUDE_CLI_PROFILE_ID, readClaudeCliCredentialsCached, and EXTERNAL_CLI_SYNC_TTL_MS are all pre-existing exports — no new dependencies

@starvex
starvex requested a review from a team as a code owner April 24, 2026 03:09
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS labels Apr 24, 2026
@greptile-apps

greptile-apps Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR registers claude-cli in EXTERNAL_CLI_SYNC_PROVIDERS so the auth store automatically syncs OAuth tokens from the macOS Keychain / ~/.claude/.credentials.json, mirroring the existing minimax-portal pattern.

  • Type mismatch: readClaudeCliCredentialsCached returns ClaudeCliCredential | null, a discriminated union that includes a type: \"token\" variant incompatible with the () => OAuthCredential | null signature required by ExternalCliSyncProvider. The minimax-portal entry avoids this because MiniMaxCliCredential is always type: \"oauth\". This is likely a TypeScript compile error and, at runtime, would produce a structurally incomplete "OAuthCredential" if Claude CLI ever issues a non-OAuth token.

Confidence Score: 3/5

Not safe to merge without resolving the ClaudeCliCredential / OAuthCredential type incompatibility.

There is a P1 type mismatch: ClaudeCliCredential includes a type: "token" variant that is not assignable to OAuthCredential. This would cause a TypeScript compile error and a silent runtime bug if a token credential is ever returned. A one-line guard is all that is needed to fix it.

src/agents/auth-profiles/external-cli-sync.ts — the readCredentials lambda needs a type guard on the "oauth" variant before the PR can safely land.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/auth-profiles/external-cli-sync.ts
Line: 78

Comment:
**Type mismatch: `ClaudeCliCredential` is not assignable to `OAuthCredential`**

`readClaudeCliCredentialsCached` returns `ClaudeCliCredential | null`, but `ExternalCliSyncProvider.readCredentials` is typed as `() => OAuthCredential | null`. `ClaudeCliCredential` is a discriminated union that includes a `{ type: "token"; token: string; expires: number; }` variant, which is structurally incompatible with `OAuthCredential` (which requires `type: "oauth"`, `access`, and `refresh`). TypeScript should reject this assignment.

At runtime, if Claude CLI ever returns a `type: "token"` credential, it would be silently pushed into `profiles` as a fake `OAuthCredential` with missing `access`/`refresh` fields, potentially breaking downstream refresh logic.

The fix is to filter only the `"oauth"` variant at the call site:

```suggestion
    readCredentials: () => {
      const creds = readClaudeCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS });
      return creds?.type === "oauth" ? creds : null;
    },
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(auth): add Claude CLI to EXTERNAL_CL..." | Re-trigger Greptile

Comment thread src/agents/auth-profiles/external-cli-sync.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60725c8a61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/auth-profiles/external-cli-sync.ts Outdated
@Stache73

Copy link
Copy Markdown

+1 — hitting this on Windows 11 as well, which rules out macOS-Keychain-specific quirks. The same stale-snapshot pattern applies via the .credentials.json file path rather than Keychain.

Environment

  • OpenClaw 2026.4.22 (00bd2cf), fresh install from today
  • Windows 11, PowerShell 5.1 (non-elevated)
  • Node v24.6.0, npm 11.11.1
  • Claude CLI installed via npm at C:\Users\arvid\AppData\Roaming\npm\claude.ps1

Diagnostic path matched the PR description almost verbatim

  1. openclaw models auth login --provider anthropic --method cli --set-default → succeeded, profile anthropic:claude-cli (claude-cli/oauth) registered.
  2. openclaw doctor immediately flagged anthropic:claude-cli: expired (0m).
  3. Verified by dumping the profile shape (values redacted): type=oauth, provider=claude-cli, access/refresh length 108 each, expires=1776562115377 (about an hour old already — tokens from the initial login landed just barely valid, then doctor's read was slightly after expiry).
  4. Ran claude -p "reply with pong" directly — returned pong immediately, so Claude Code itself silently refreshed its tokens between login and that call.
  5. Ran openclaw doctor again — same expired (0m) warning, expires value unchanged in auth-profiles.json. Confirms the read-back path is missing on Windows.

Where this shows up in doctor output

o  OAuth refresh errors -------
|  - anthropic:claude-cli: OAuth refresh failed - ... (when it tries)
o  Model auth -------
|  - anthropic:claude-cli: expired (0m) - `openclaw configure`.

In my case, configure --section model runs cleanly but does not refresh the stale snapshot — it only refreshes the model catalog for the CLI backend, not the cached credentials. So the doctor warning is persistent across configure runs.

Impact for Claude Max users on Windows
Identical to the macOS case described in the PR: primary=claude-cli/claude-opus-4-7 + fallbacks to API-key profiles means any request arriving during the stale window hits silent fallback to metered API billing. Not easy to notice until the Anthropic Console shows charges.

Happy to help test the fix once it lands — I already have a stale-snapshot repro standing by on this machine.

@obviyus
obviyus force-pushed the fix/claude-cli-external-sync branch from 066f343 to e57102f Compare April 25, 2026 11:36

@obviyus obviyus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the Claude CLI auth-sync bug: refreshed Claude CLI OAuth credentials were readable but not registered in the external CLI sync provider list.

Maintainer follow-up: rebased onto current main, kept the provider path unified with existing external CLI sync, removed the excessive provider-normalization comment, added focused regression coverage, and resolved the outdated review threads.

Local gate: targeted auth tests plus the manual equivalent of check:changed lanes: conflict markers, core/core-test tsgo, lint:core, import cycles, auth guards, and 22 changed test files / 262 tests.

@obviyus
obviyus merged commit 150f3e4 into openclaw:main Apr 25, 2026
56 checks passed
@obviyus

obviyus commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Landed on main.

Thanks @starvex.

Angfr95 pushed a commit to Angfr95/openclaw that referenced this pull request Apr 25, 2026
ayesha-aziz123 pushed a commit to ayesha-aziz123/openclaw that referenced this pull request Apr 26, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants