#7523: fix(auth): re-sync external CLI credentials on token revocation
agents
Cluster:
Auth Improvements and Fixes
## Summary
- When an external CLI (e.g. Claude Code) refreshes its OAuth token independently, the old token is revoked (Anthropic uses single-use refresh tokens). OpenClaw's stored copy becomes invalid, causing `HTTP 403: permission_error: OAuth token has been revoked`.
- The existing sync only re-reads from the CLI when the stored token is near-expiry, not when it's been revoked — revocation ≠ expiration.
- Adds `resyncExternalCliOnAuthError()` that force-reads from external CLI credentials (bypassing the 15-minute TTL cache) when an auth error is detected, and retries with fresh tokens before marking the profile as failed.
- Also logs a warning when the write-back to Claude CLI silently returns `false` (file missing or structure invalid) instead of swallowing the failure.
## Changes
- **`external-cli-sync.ts`**: New `resyncExternalCliOnAuthError()` — force re-reads from Claude CLI / Qwen CLI / MiniMax CLI on auth errors, applies fresh tokens if different and still valid.
- **`auth-profiles.ts`**: Barrel export for the new function.
- **`run.ts`**: In both prompt-error and assistant-error paths, attempts re-sync before marking auth failure and rotating profiles. If fresh tokens are found, re-applies and retries.
- **`oauth.ts`**: Checks `writeClaudeCliCredentials()` return value and logs when write-back fails silently.
## Test plan
- [x] `pnpm build` passes
- [x] `pnpm check` passes (lint clean on changed files)
- [x] `pnpm test` — 881 tests pass across 73 test files
- [ ] Manually verify: revoke token in Claude CLI, confirm OpenClaw recovers immediately instead of entering cooldown
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
PR adds a recovery path when OpenClaw hits OAuth `permission_error` due to token revocation from external CLIs (Claude Code/Qwen/MiniMax). It introduces a `resyncExternalCliOnAuthError()` that bypasses the existing TTL cache to force re-reading external CLI credential files, then updates the active profile tokens and retries before marking a profile as failed/rotating. It also logs when `writeClaudeCliCredentials()` returns `false` (previously silent), improving observability around failed write-back.
These changes fit into the existing auth profile sync flow by extending `external-cli-sync.ts`’s read/write integration and hooking the runner’s error handling in `pi-embedded-runner/run.ts` so that revocation (a non-expiry failure mode) triggers a one-time refresh from the external source rather than immediately putting the profile into cooldown.
<h3>Confidence Score: 4/5</h3>
- This PR is generally safe to merge and improves recovery from external CLI token revocation, with a small logic edge case around token re-sync skipping updates when only expiry changes.
- Core changes are localized and follow existing patterns (force-read external CLI creds on auth errors, retry before marking failures). The main concern is the Claude token resync branch’s equality check ignoring expiry/metadata changes, which could cause missed updates in some environments. Minor observability issue from swallowing apply errors.
- src/agents/auth-profiles/external-cli-sync.ts (Claude token resync logic); src/agents/pi-embedded-runner/run.ts (swallowed applyApiKeyInfo errors)
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#2123: fix(auth): sync from Claude CLI keychain before OAuth refresh
by jorge123255 · 2026-01-26
87.4%
#13484: feat(auth): restore Claude Code CLI OAuth credential sync
by joshpocock · 2026-02-10
83.8%
#4550: fix: sync google-gemini-cli-auth tokens from external CLI (#3803)
by SalimBinYousuf1 · 2026-01-30
81.9%
#10492: fix(auth): store Anthropic setup-token as type:oauth for auto-refresh
by sparck75 · 2026-02-06
80.4%
#21518: feat(auth): add Anthropic OAuth token refresh and fix external CLI ...
by maxtongwang · 2026-02-20
79.6%
#8469: fix(auth): detect actual keychain account name when writing Claude ...
by adam-smeth · 2026-02-04
78.5%
#8729: feat(auth): sync OpenAI Codex CLI credentials into auth store
by thosvesta · 2026-02-04
78.0%
#3196: docs: clarify auth-profiles.json format for Claude Max setup-tokens
by aadeina · 2026-01-28
77.5%
#8225: feat(auth): add forceRefresh option and invalidateOAuthToken for 40...
by arodundef · 2026-02-03
77.2%
#17531: fix(auth): sync Codex CLI credentials into auth profile store and c...
by sauerdaniel · 2026-02-15
77.1%