OAuth tokens not invalidated on plan downgrade (security/billing issue)
Bug Report: OAuth tokens not revoked on plan downgrade
Summary
When downgrading a Claude subscription plan (e.g., Max -> Pro), existing OAuth tokens issued to Claude Code CLI are not immediately invalidated or refreshed. The old token retains the previous plan's subscriptionType and rateLimitTier claims until it naturally expires and triggers a refresh. During this window (up to ~30 days), the CLI continues operating with the previous plan's elevated rate limits and features, despite the user no longer paying for them.
Environment
- Claude Code CLI on Windows 11 Pro
- Credential file:
~/.claude/.credentials.json - Previous plan: Claude Max (subscriptionType:
max, rateLimitTier:default_claude_max_5x) - Current plan: Claude Pro
- Downgrade date: Early March 2026
Steps to reproduce
- Subscribe to Claude Max plan
- Log in to Claude Code CLI (
claude login) -- this creates an OAuth token stored in~/.claude/.credentials.json - Downgrade the subscription from Max to Pro via the Anthropic billing page
- Open Claude Code CLI on the same machine (without logging out/in)
- Inspect
~/.claude/.credentials.json
Expected behavior
After a plan downgrade, either:
- Existing OAuth tokens should be revoked server-side, forcing a re-authentication that issues a token matching the current plan
- Or the token's
subscriptionTypeandrateLimitTierclaims should be validated server-side on each request against the current billing state, regardless of what the local token says
Actual behavior
The token in .credentials.json still contains:
{
"subscriptionType": "max",
"rateLimitTier": "default_claude_max_5x",
"expiresAt": 1773278743369
}
expiresAtcorresponds to April 9, 2026 -- approximately 30 days after token issuance- The CLI continues to operate with Max-tier rate limits despite the account being on the Pro plan
- The token will remain valid until natural expiration
Observed behavior across multiple machines
The user runs Claude Code on multiple machines. After the downgrade:
- Other machines: Their access tokens had already expired naturally before or shortly after the downgrade. When Claude Code performed a routine token refresh, the server issued new tokens with Pro-tier claims — automatically and without any manual logout/login. This is the correct behavior.
- This machine: The access token was issued more recently and had not yet expired at the time of the downgrade. No refresh was triggered, so the token still carries Max-tier claims. It will remain valid until its natural expiration on April 9, 2026.
This confirms that the refresh mechanism does update the plan claims correctly, but the problem is that no forced refresh or revocation is triggered server-side when a plan change occurs. The window of elevated access depends entirely on where each token sits in its TTL cycle — up to ~30 days.
Security / Billing implications
- Billing integrity: Users who downgrade continue consuming higher-tier resources without paying, for up to 30 days depending on token TTL
- No server-side enforcement at request time: Rate limit tier appears enforced based on token claims rather than real-time billing state — the local token is the sole source of truth
- No forced refresh on plan change: The refresh mechanism works correctly (new tokens get proper claims), but plan changes do not trigger a forced refresh or revocation of existing tokens. This is the core issue.
- Unpredictable window: The duration of elevated access depends on when each machine's token was last refreshed — making it inconsistent and hard to detect
Suggested fix
- Best fix: On plan change, invalidate all existing access tokens server-side, forcing an immediate refresh on next CLI usage. The refresh mechanism already issues correct claims — it just needs to be triggered.
- Alternative: Validate subscription state server-side on each API call rather than trusting token claims (more costly but more robust)
- Mitigation: Reduce access token TTL significantly (e.g., from ~30 days to a few hours) so the refresh cycle catches plan changes faster
Evidence available
A copy of the affected .credentials.json file (containing the stale Max-tier token) has been preserved and can be provided to Anthropic upon request. The access token and refresh token in the file can be used to identify the associated user account and verify the plan mismatch server-side.
Contact: gnomenux@gmail.com
Workaround
Users can manually run claude logout followed by claude login to force a new token with current plan claims. Alternatively, waiting for the token's natural expiration will trigger a refresh that also resolves the issue.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗