[BUG] Windows/VS Code: OAuth refresh token rejected (HTTP 400) after every sleep/wake since 2.1.247, forces /login daily (refresh raced at extension startup or lost mid-flight)
Preflight Checklist
- [x] I have searched existing issues. Closest related (all closed as "not planned"/duplicate, none explains the pattern below completely): #54443, #48786, #43392, #25609, #24317, #68660, #34306, #61676
- [x] I have read the troubleshooting guide and the authentication docs (credential management, "renew an expiring login", the v2.1.211 sleep/wake fix)
- [x] This is a single, reproducible problem with full local evidence (extension log lines, transcript entries, Windows power event log)
What's Wrong?
Since 2026-08-27 (extension 2.1.247 → 2.1.251) the VS Code extension on Windows forces a full /login every time the machine comes back from hybrid shutdown / sleep: the first refresh attempt after wake is rejected by the server with HTTP 400 ("OAuth refresh token is no longer valid; run /login"). Between 2026-08-21 and 2026-08-27 (2.1.238 → 2.1.246) a single credential chain survived 7 days of the same daily sleep/wake cycle, so this is a regression.
Nothing local deletes or corrupts the credentials: ~/.claude/.credentials.json is intact after every incident (refresh token refreshTokenExpiresAt ~4 weeks ahead, expiresAt 8 h after login), no CLAUDE_CONFIG_DIR, no ANTHROPIC_API_KEY, the config dir is not on OneDrive, Defender logged no detections or Controlled Folder Access blocks, ~/.claude.json is healthy (71 KB), only the extension is used (no standalone CLI sessions), no other machine has a copy of the credentials.
Timeline (local time, UTC+2), all from %APPDATA%\Code\logs\<start>\window1\exthost\Anthropic.claude-code\Claude VSCode.log plus the Windows System event log (Kernel-Power 42/107, Power-Troubleshooter 1). The machine has Fast Startup enabled, so "shut down" is a hybrid shutdown that kills VS Code and every claude.exe in the same second the logs stop (e.g. last log line 23:47:11, sleep event 23:47:19); the OS was never cold-booted in the whole period.
| wake | VS Code start | what the extension logged | consequence |
|---|---|---|---|
| 08-27 21:19:06 | 21:22:04 (extension auto-updated 21:22:11 → 21:22:19) | 21:22:15 [ERROR] OAuth refresh failed (expected): Request failed with status code 400 | sessions kept working that evening on the still-valid access token; next morning: |
| 08-28 07:01:44 | 07:18:45 | 07:20:58 API error (attempt 1/11): OAuth refresh token is no longer valid; run /login | /login |
| 08-29 07:10:28 | 07:13:13 | 07:13:46 GrowthBook: pre-init OAuth refresh failed (timeout) then 07:50:54 OAuth refresh failed (expected): 400 | /login |
| 08-29 20:20:46 | 22:30:51 (extension auto-updated 2.1.250 → 2.1.251 at 22:30:59 → 22:31:12) | 22:31:28 OAuth refresh token is no longer valid | /login |
| 08-30 06:42:47 | 06:45:09 | 06:45:24 OAuth refresh failed (expected): 400 (9 s after start) → 07:01:43 Failed to authenticate: OAuth session expired and could not be refreshed | /login |
Observations that narrow the mechanism:
- Every failure is the first refresh after the access token expired while the machine was asleep. Mid-day refreshes while processes stay alive succeed (e.g. 08-28: login 07:20, access token expired 15:20, sessions at 18:41 to 22:31 worked without any error).
- The 400 is returned on the very first attempt after wake (08-30 06:45:24, 9 s after VS Code start, no earlier attempt logged), i.e. the refresh token stored in the file the previous evening was already consumed server-side.
- On extension start the extension spawns two
claude.exeprocesses a few seconds apart ("Loading config cache by launching Claude (no channel)" + the session process;AuthManager initializedat 06:45:15 and 06:45:21). With an expired access token both need a refresh at once. The message textOAuth refresh failed (expected)suggests the code itself anticipates the losing side of that race. If the server applies refresh-token reuse detection, the loser's reuse of the old token revokes the whole chain, which is exactly what the next wake observes. - 08-29 07:13:46 shows the other variant: a refresh that timed out 33 s after start (network not yet up after wake). If the server had already rotated the token, the client keeps the consumed one and the next attempt (07:50) gets 400.
- Two of the five incidents coincide to the minute with the extension auto-updating itself at startup (08-27 21:22, 08-29 22:31), which restarts the extension host and kills any
claude.exethat may be mid-refresh (the binary's own message "another Claude Code process is refreshing it or exited mid-refresh" describes that case, but it was never logged; the file was simply left with a dead token). - The extension host has its own
AuthManager.refreshOAuthToken()inextension.js(read.credentials.json→ POST/v1/oauth/token→saveOAuthTokens) that does not take the cross-process refresh lock theclaude.exebinary uses. It did not fire in this period (no "expiring soon, refreshing"/"Saving OAuth tokens" lines), so it is not the cause here, but it is a latent second racer. - A successful refresh is not logged at all, so from the client side there is no way to tell which process wrote the token that later turned out to be dead. Logging refresh attempts/results (with a hash of the token, never the token) would make this class of bug diagnosable.
What Should Happen?
A saved login should survive sleep/wake. Concretely:
- only one refresh per machine at a time, including the config-cache loader process and the extension host's own
AuthManager(single-flight across all writers of.credentials.json); - do not refresh at startup when the stored access token is still valid (the pre-init/bridge refresh should reuse a valid token);
- on a refresh timeout, retry with backoff (and re-read the file first) before declaring the token dead; a refresh whose response was lost currently turns into a forced
/login8 hours later; - if reuse detection is what revokes the chain server-side, tolerate a reuse of the immediately previous refresh token for a short grace window (RFC 6819 §5.2.2.3 style), or at least return a distinguishable error so the client can explain what happened instead of "session expired".
Error Messages/Logs
2026-08-27 21:22:15.783 [ERROR] OAuth refresh failed (expected): Request failed with status code 400
2026-08-28 07:20:58.186 [ERROR] API error (attempt 1/11): OAuth refresh token is no longer valid; run /login to re-authenticate
2026-08-28 07:20:58.198 [ERROR] API auth_error: OAuth refresh token is no longer valid; run /login to re-authenticate
2026-08-29 07:13:46.745 [DEBUG] GrowthBook: pre-init OAuth refresh failed (timeout)
2026-08-29 07:50:54.402 [ERROR] OAuth refresh failed (expected): Request failed with status code 400
2026-08-29 07:50:55.124 [ERROR] API error (attempt 2/11): OAuth refresh token is no longer valid; run /login to re-authenticate
2026-08-29 22:31:28.833 [ERROR] API error (attempt 1/11): OAuth refresh token is no longer valid; run /login to re-authenticate
2026-08-30 06:45:15.250 [info] AuthManager initialized
2026-08-30 06:45:21.010 [info] AuthManager initialized
2026-08-30 06:45:24.759 [ERROR] OAuth refresh failed (expected): Request failed with status code 400
2026-08-30 06:45:24.775 [DEBUG] [bridge:repl] Skipping: OAuth token expired and refresh failed (re-login required)
2026-08-30 06:45:24.775 [DEBUG] [bridge:sdk] State change: failed — /login
2026-08-30 06:45:24.835 [error] Remote Control auto-enable failed: Error: /login
2026-08-30 06:45:26.156 [DEBUG] [Bootstrap] No auth available on retry, aborting
2026-08-30 07:01:42.989 [ERROR] API error (attempt 1/11): OAuth refresh token is no longer valid; run /login to re-authenticate
Transcript entry (~/.claude/projects/<project>/<session>.jsonl, 2026-08-30T05:01:43Z, version 2.1.251, entrypoint claude-vscode):
"text": "Failed to authenticate: OAuth session expired and could not be refreshed", "error": "authentication_failed", "isApiErrorMessage": true
Earlier occurrences of the same authentication_failed in transcripts (UTC): 08-16 05:38 (2.1.233, "401 OAuth access token has expired"), 08-19 04:09 (2.1.234), 08-21 12:22 (2.1.237), then daily from 08-28 (2.1.247, 2.1.250, 2.1.251). No occurrences 08-22 → 08-27.
Windows power events for the last incident: SLEEP 2026-08-29 23:47:19, WAKE 2026-08-30 06:42:47; no boot/shutdown (Kernel-General 12/13) events since 08-26.
Steps to Reproduce
- Windows 11 with Fast Startup enabled (hybrid shutdown) or plain sleep, VS Code with the Claude Code extension, logged in with a claude.ai subscription (OAuth).
- Work in the extension, then "shut down"/sleep the machine with VS Code still open (processes are killed at that instant).
- Wake it after the 8 h access-token lifetime has passed (typically next morning) and open VS Code.
- Watch the extension output:
OAuth refresh failed (expected): Request failed with status code 400; the first prompt fails withOAuth session expired and could not be refreshedand/loginis required. .credentials.jsonstill contains the refresh token written at the previous login (mtime unchanged since that login).
Claude Model
Other (Claude Fable 5; the model is irrelevant, the failure is in auth)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.246 (one credential chain survived 2026-08-21 → 2026-08-27 across daily sleep/wake on 2.1.238 → 2.1.246); failing daily since 2.1.247 through 2.1.251
Claude Code Version
2.1.251 (binary bundled in the VS Code extension anthropic.claude-code-2.1.251-win32-x64); a standalone native install 2.1.229 exists in ~\.local\bin but is not used for any session
Platform
Anthropic API (claude.ai subscription OAuth login)
Operating System
Windows (Windows 11 Home 10.0.26200, Fast Startup / hybrid shutdown, Modern Standby)
Terminal/Shell
Other (VS Code extension native UI, claudeCode.useTerminal off; shell for tools is Git Bash)
Additional Information
- VS Code 1.135.0; the extension auto-updates on startup (
Auto updating outdated extensions. anthropic.claude-codein VS Codemain.log), which restarted the extension host in the same minute as two of the incidents. - Remote Control auto-enable runs at every session start and performs its own refresh attempt (
[bridge:repl],[remote-bridge] Fetched bridge credentials), adding one more refresh caller at startup. - Another extension on the same machine (OpenAI Codex) logs the same class of failure after wakes ("refresh token was already used"), which points at refreshes being killed mid-flight by the hybrid shutdown rather than at anything specific to one vendor's client, but the Claude Code client currently has no recovery path for it.
- Happy to run a debug build, enable extra logging, or provide the full extension log files privately.