#8225: feat(auth): add forceRefresh option and invalidateOAuthToken for 401 recovery
agents
stale
Cluster:
Auth Improvements and Fixes
## Summary
When an OAuth token is revoked server-side before the local `expires` timestamp, the system keeps using the stale cached token until local expiry passes. This causes persistent 401 errors that require manual intervention.
## Changes
- Add `forceRefresh?: boolean` parameter to `resolveApiKeyForProfile`, `resolveApiKeyForProvider`, and related functions
- Add `invalidateOAuthToken()` function to mark a token as needing refresh (sets `expires: 0`)
- Export `invalidateOAuthToken` from `model-auth.ts` for external use
## Usage
Callers can now recover from 401 errors by:
```typescript
// Option 1: Retry with forced refresh
const auth = await resolveApiKeyForProvider({
provider: "anthropic",
forceRefresh: true, // Skip local expires check
});
// Option 2: Permanently invalidate for next use
invalidateOAuthToken({ profileId: "anthropic:claude-cli" });
```
## Root Cause
The `refreshOAuthTokenWithLock` function checks `Date.now() < cred.expires` and returns cached credentials if they appear valid locally. However, the server may have revoked the token (for security reasons, session limits, etc.) before this timestamp.
## Testing
Manual testing performed. The changes are additive and backward-compatible - existing code paths are unchanged when `forceRefresh` is not specified.
Fixes #8223
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR adds a `forceRefresh?: boolean` option to the OAuth API key resolution path so callers can bypass the local `expires` check after a provider-side revocation (e.g., retrying after a 401). It also introduces `invalidateOAuthToken()` which marks a stored OAuth credential as expired (`expires: 0`) to force a refresh on next use, and re-exports that helper through `auth-profiles.ts` / `model-auth.ts` for external callers.
The approach is consistent with the existing cached-token flow in `auth-profiles/oauth.ts` (the `Date.now() < cred.expires` fast-path), and the change is additive when `forceRefresh` is omitted.
<h3>Confidence Score: 4/5</h3>
- This PR is largely safe to merge, with a couple of small issues to address.
- Core logic change (skipping the local cache when `forceRefresh` is set) is straightforward and localized. The main concerns are an unused import that may fail lint/TS checks and a potential race condition because invalidation writes the auth store without using the same file lock as refresh.
- src/agents/model-auth.ts, src/agents/auth-profiles/oauth.ts
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#5027: fix(auth): use correct OAuth credentials for google-gemini-cli refresh
by shayan919293 · 2026-01-30
79.1%
#2123: fix(auth): sync from Claude CLI keychain before OAuth refresh
by jorge123255 · 2026-01-26
78.8%
#3909: fix(auth): refresh all OAuth profiles per provider
by Daviey · 2026-01-29
78.7%
#8677: fix: add retry logic to OAuth token refresh
by skyblue-will · 2026-02-04
77.5%
#7523: fix(auth): re-sync external CLI credentials on token revocation
by codeslayer44 · 2026-02-02
77.2%
#8805: [Bug Fix][AI-assisted] Refresh Copilot token before expiry and retr...
by Arthur742Ramos · 2026-02-04
75.9%
#11821: fix(auth): trigger failover on 401 status code from expired OAuth t...
by AnonO6 · 2026-02-08
75.5%
#10492: fix(auth): store Anthropic setup-token as type:oauth for auto-refresh
by sparck75 · 2026-02-06
74.6%
#22105: feat(auth): add refreshable Anthropic OAuth login flow
by sauerdaniel · 2026-02-20
74.2%
#13484: feat(auth): restore Claude Code CLI OAuth credential sync
by joshpocock · 2026-02-10
72.8%