Repository navigation
Refresh the token like Claude Code does: current endpoint, shared lock, recover on 401 - #23
Merged
Merged
Conversation
The Claude desktop app hands Claude Code its own token and never writes ~/.claude/.credentials.json, so the file only stays fresh while the CLI is in use. Clawdmeter now refreshes the way the CLI does: - refresh 5 min before expiry instead of after it lapses - use the current token endpoint (platform.claude.com), falling back to the legacy console host, and send the stored scopes - take Claude Code's .oauth_refresh.lock and re-read the file under it, standing down if the tokens were already rotated, so the two never spend the same refresh token - on a 401 with a valid-looking expiry, re-read / force a refresh and re-poll right away instead of waiting out the expiry - remember refresh tokens the endpoint rejected and stop resending them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsZHk76Z7ym9WENv2U2MMy
Develop's fix/token-expired-badge covers the same desktop-app diagnosis (early refresh, RefreshOutcome backoff, rejected-token block, Sign in again). Keep that structure and layer on what it lacked: the current token endpoint and stored scopes, Claude Code's refresh lock with a BUSY outcome, standing down when the tokens were rotated under us, and an immediate refresh + re-poll on a 401. The per-run dead-token set is dropped in favour of the poller's REJECTED block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsZHk76Z7ym9WENv2U2MMy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
When Claude Code runs from the desktop app, the app keeps its own login and passes the token straight to Claude Code, so nothing renews
~/.claude/.credentials.jsonany more; in the terminal, theclaudeCLI kept it fresh as a side effect.fix/token-expired-badge(already on develop) handles most of this: it refreshes 30 minutes early, backs off based on the failure's cause, stops after a rejected refresh token, and adds "Sign in again". This PR adds what that branch left out, based on reading how the current Claude Code release (2.1.282) refreshes.Changes
platform.claude.com/v1/oauth/token, which the CLI uses now.console.anthropic.comis kept only as a fallback when the new address can't be reached or answers 404, 405 or a redirect. The request also carries the credentials' storedscopesandclientId, the way the CLI's does.~/.claude/.oauth_refresh.lock(the same mkdir-based lock the CLI uses, stale after 60s) plus the older~/.claude.lock. Once it holds the lock it re-reads the file. If the access token has changed since it looked, Clawdmeter stands down, because the CLI (or another Clawdmeter) already refreshed and a refresh token only works once.RefreshOutcome.BUSY. Returned when another process holds the lock. An automatic refresh that gets BUSY stays quiet and doesn't back off.auth-expired(a 401), the poller re-reads the file and re-checks usage if the token there is newer. Otherwise it refreshes, within the usual cooldown and refresh block, and re-checks immediately instead of waiting for the next poll.refresh()keeps develop's contract: it always refreshes unless the token changed while it waited for the lock.Testing
python -m pytest tests -q: 712 passed, 3 skipped.tests/test_token_refresh_coexist.pycovers: the endpoint and scopes in the request, the fallback to the old address, a held lock (BUSY, no network call), taking over a stale lock, standing down when the token was already rotated, a rejected refresh token reporting REJECTED, a BUSY automatic refresh staying silent, and recovering from a 401 when the stored expiry looked valid.macOS is unchanged: the Keychain is still read-only.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LsZHk76Z7ym9WENv2U2MMy
Generated by Claude Code