Repository navigation
[Bug]: Windows: Claude usage shows "OAuth access token has expired. Re-authenticate" — Orca never refreshes the managed token (refresh token still valid, no live PTY, no recovery path on win32) #18463
Description
Operating system
Windows
Orca version
1.4.195 (Windows 11 Pro 24H2, x64)
Details
Short summary
On Windows, Orca's status-bar Claude usage meter reports OAuth access token has expired. Re-authenticate to continue. even though the managed account's refresh token is still valid for weeks. Orca simply never refreshed the access token: both managed accounts were last refreshed at 09:29, the access tokens expired at 17:29 (8h TTL), and at 19:20 Orca was still running, still holding the expired tokens, with no live Claude PTY. Meanwhile a separate OAuth session for the same Anthropic account, kept by another tool with its own token store, was refreshed on schedule and returned 200 from the same usage endpoint the whole time — so the account itself is healthy.
This is distinct from #9582 / #13165 (macOS, multi-store clobber, token family revoked). No clobber happened here: ~/.claude/.credentials.json and the managed .credentials.json were byte-identical and both untouched since 09:29. The token is not broken, it is just never refreshed, and on Windows there is no recovery path after the resulting 401.
What happened (evidence, all timestamps local, today)
| managed account A | managed account B | |
|---|---|---|
.credentials.json mtime (%APPDATA%\orca\claude-accounts\<id>\auth\) |
09:29:18 | 09:29:04 |
claudeAiOauth.expiresAt |
17:29:16 | 17:29:02 |
claudeAiOauth.refreshTokenExpiresAt |
~4 weeks out | ~4 weeks out |
GET https://api.anthropic.com/api/oauth/usage with that access token at 19:21 |
401 {"type":"authentication_error","message":"OAuth access token has expired. Re-authenticate to continue."} |
same 401 |
| same account, independent OAuth session held by another local tool (own refresh token, refreshed at 19:17–19:22) | 200, 5h/7d utilization returned |
200 |
Other state at the time:
- Orca.exe running (6 processes), main window in use (this was captured from a terminal inside Orca).
orca-data.json→claudeLivePtySessionIds: []; noclaude/claude.exeprocess on the machine (only non-Claude agents in panes). SohasLiveClaudePtys()was false and the "wait for the live Claude terminal to rotate" deferral does not apply.settings.activeClaudeManagedAccountId= account B;~/.claude/.credentials.jsonis byte-identical to account B's managed file (Orca's materialized copy), mtime 09:29:25 — nothing else wrote it all day.daemon.logandmain.trace.ndjson*contain zero auth/refresh/usage entries, so from the filesystem it is impossible to tell whether the proactive refresh was never attempted or attempted and failed.
Why this can happen (reading origin/main today)
- The only proactive refresh is
refreshManagedAccountTokenIfNeededinsidedoSyncForCurrentSelection(src/main/claude-accounts/runtime-auth/runtime-auth-sync.ts:244-257). Sync runs on startup, account switch,prepareForClaudeLaunch, andprepareForRateLimitFetch. If none of those fire for 8h, the token expires. - Background polling is focus-gated:
service-polling.ts:60-69shouldBackgroundPoll()requiresisVisible() && !isMinimized() && isFocused(), 15-minute interval. Working in another window all afternoon means no fetch, no sync, no refresh. ([Bug]: Usage status bar stops auto-refreshing after an update relaunch (focus-gated poll), separate from the #5355 auth path #5627 was closed, but this gate is still there.) refreshClaudeOauthCredentials(src/main/claude-accounts/oauth-refresh.ts:141-170) is best-effort: any non-2xx or network error isconsole.warn+return null, the caller keeps the expired blob, and nothing records the failure inusageMetadata. On Windowsconsole.warnfrom the main process goes nowhere the user can see.- Windows has no recovery path after the 401.
service-fetch-targets.ts:133-146returnsfalseforshouldAllowClaudePtyFallbackandshouldAllowClaudeUsagePanelSupplementonwin32. Inclaude-active-usage-fetch.ts:98-118bothrepairClaudeCredentialsThenRetryOAuthandfetchClaudeUsageViaCliare gated onallowCliFallback, so on Windows astale-tokenclassification goes straight to the error result. On macOS/Linux the PTY fallback would let the CLI rotate the token; on Windows the meter just says "Re-authenticate" while the refresh token is perfectly usable.
Net effect on Windows: Orca is the sole owner of this token lineage (it materializes it into ~/.claude itself), yet it only refreshes opportunistically, fails silently, and has no fallback. The "re-authenticate" prompt is misleading — a plain grant_type=refresh_token call would have fixed it.
How to reproduce
- Windows, add a managed Claude account (subscription OAuth) and make it active.
- Do not open a
claudepane in Orca. Keep Orca running but work in other applications (or otherwise avoid the focus-gated poll) for > 8 hours. - Come back: the Claude usage chip shows
OAuth access token has expired. Re-authenticate to continue. - Check
%APPDATA%\orca\claude-accounts\<id>\auth\.credentials.json:expiresAtis in the past,refreshTokenExpiresAtis weeks away.
Expected
- With a valid refresh token and no live Claude PTY, Orca should refresh the access token before/at expiry regardless of window focus, or at least on the first usage fetch that returns 401 — the pure-HTTP
refreshClaudeOauthCredentialsalready exists and needs no PTY, so the WindowsallowCliFallbackgate should not block it. - A failed refresh should surface its HTTP status (or "network") in
usageMetadata/ the chip instead of collapsing into "Re-authenticate".
Related
- [Bug]: anthropic 401 #9582, [Bug] frequent claude code login for multi account setup #13165 — multi-store clobber / 401 loop on macOS (different mechanism; no clobber observed here)
- [Bug]: (Does not affect normal use) Claude Code usage status stuck on "Refreshing sign-in" and does not update #8974 / fix(status-bar): escalate stuck Claude "Refreshing sign-in" after timeout #10060 — UI stuck on "Refreshing sign-in"
- [Bug]: Usage status bar stops auto-refreshing after an update relaunch (focus-gated poll), separate from the #5355 auth path #5627 — focus-gated poll (closed, gate still present)
- [Feature]: Support
claude setup-tokenfor managed Claude accounts #12002 —setup-tokensupport would sidestep refresh entirely
Happy to open a PR that adds a PTY-free delegated refresh path (call refreshClaudeOauthCredentials on the managed credentials, persist via writeClaudeManagedCredentialsJson, retry the OAuth usage request) and records refresh failures in usageMetadata, if maintainers are open to it given the pending auth PRs (#9754, #6864).
Activity
Minidoracat commentedon Sep 3, 2026
Follow-up / workaround (confirms the token itself is fine)
I verified the failure is only in Orca's refresh path, not the account: feeding Orca through its existing live-session channel makes the meter recover instantly, with no login.
POST http://127.0.0.1:$ORCA_AGENT_HOOK_PORT/statusline/claude(form-encoded,X-Orca-Agent-Hook-Token, fieldspaneKey,configDir=,env,version,payload=<JSON with rate_limits.five_hour / seven_day>) — i.e. exactly what the managed Claude Code statusLine script sends — returns204,ingestLiveClaudeRateLimitsaccepts it, and the chip goes from "Re-authenticate" to live 5h/7d percentages. BecauseisLiveClaudeUsageFreshshort-circuits the automated fetch for 5 minutes, posting every ~4 minutes keeps the bar alive indefinitely. It also clears therate-limitedstate Orca gets into after opening the account switcher (each inactive-account preview on Windows fires the usage endpoint with that account's expired managed token → 401s, then 429), since the live-session ingest is not gated byretryAtMs.What I'm running as a stopgap: a small extension in another local agent that already holds an independent OAuth session for the same account (own refresh token, refreshed with the usual 5-minute skew). Every 4 minutes it calls
/api/oauth/usagewith that token and posts the result to the endpoint above. No writes to Orca's managed credentials or~/.claude, so none of the #9582 / #13165 clobber risk.Two observations from doing this that may help the fix:
refreshClaudeOauthCredentialsis all that is needed on Windows; nothing in the recovery path actually requires a PTY for a managed account.fetchInactiveClaudeAccountsOnOpen) are a cheap way to burn the usage endpoint's rate limit on Windows: they use each account's never-refreshed managed token, so every open of the switcher is N guaranteed 401s. Gating them onexpiresAt(or refreshing first) would avoid the 429 → "Claude usage is rate limited right now" that follows.