Windows: refresh-token rotation not persisted to .credentials.json; failed refresh blanks the store, locking out all fresh processes (2.1.220)

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Sep 2, 2026

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 --print scheduled task under the same user account

Timeline (all times local, measured from file metadata and task logs)

  1. Aug 31 ~16:49 - last interactive claude auth login on the machine.
  2. Sep 1 16:49 - refreshTokenExpiresAt in .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).
  3. Sep 2 00:05:04 - .credentials.json mtime updates: accessToken and refreshToken now empty strings (expiresAt: 0), other fields (scopes, subscriptionType, refreshTokenExpiresAt from step 2) retained.
  4. Sep 2 00:05:10 - the scheduled headless claude --print logs 401 on every model attempted.
  5. Long-running interactive sessions from before the horizon continued working throughout (one ran 40+ hours total on the Aug 31 login).

Questions

  1. Is refresh-token rotation supposed to persist back to .credentials.json on 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.
  2. 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.
  3. troubleshoot-install.md documents 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

View original on GitHub ↗