Windows: refresh-token rotation not persisted to .credentials.json; failed refresh blanks the store, locking out all fresh processes (2.1.220)
Summary
On Windows, long-running interactive sessions stayed healthy for 40+ hours while the on-disk %USERPROFILE%\.claude\.credentials.json was never updated with rotated tokens - its refreshTokenExpiresAt remained at (last login + ~24h). When a fresh headless process (claude --print from a scheduled task, same user) started after that horizon, it failed auth AND the CLI rewrote .credentials.json with empty accessToken/refreshToken, leaving every subsequent fresh process unable to authenticate until a manual claude auth login.
Environment
- Claude Code 2.1.220 (native install,
%USERPROFILE%\.local\bin\claude.exe), autoUpdates disabled - Windows 11 Pro (always-on desktop, no sleep/wake involved)
- Claude Max (20x) subscription, OAuth login (no API key, no keychain/Credential Manager entries - file store only)
- 2-3 concurrent interactive sessions typically running, plus one nightly headless
claude --printscheduled task under the same user account
Timeline (all times local, measured from file metadata and task logs)
- Aug 31 ~16:49 - last interactive
claude auth loginon the machine. - Sep 1 16:49 -
refreshTokenExpiresAtin.credentials.json(login + ~24h). Between login and this point the file was never rewritten, while multiple long-running sessions continued working (presumably refreshing in memory). - Sep 2 00:05:04 -
.credentials.jsonmtime updates:accessTokenandrefreshTokennow empty strings (expiresAt: 0), other fields (scopes, subscriptionType,refreshTokenExpiresAtfrom step 2) retained. - Sep 2 00:05:10 - the scheduled headless
claude --printlogs 401 on every model attempted. - Long-running interactive sessions from before the horizon continued working throughout (one ran 40+ hours total on the Aug 31 login).
Questions
- Is refresh-token rotation supposed to persist back to
.credentials.jsonon Windows? Here the on-disk refresh token appears to have remained the original from login (horizon login+24h) while live sessions kept working, so any fresh process inherits a stale/superseded token. - Is rewriting the store with empty tokens on a failed refresh intended (defensive clear) or a bug? It converts one failed refresh into "every future process is locked out" with no visible error until the next launch.
troubleshoot-install.mddocuments a pre-v2.1.211 double-refresh/revocation race ("Parallel sessions... coordinate its renewal so that only one process refreshes"). This machine is on 2.1.220 - is the coordination expected to cover a scheduled-task process starting fresh at a fixed time?
Workaround in place
The scheduled task now authenticates via a claude setup-token one-year token injected as CLAUDE_CODE_OAUTH_TOKEN at runtime, which works well (and is a great facility) - filing this because the interactive-store behavior seems like it will bite anyone running scheduled headless work off a shared login.
🤖 Generated with Claude Code