Skip to content

fix(quota): never refresh or rewrite Claude Code's credentials - #306

Merged
maxthomas95 merged 2 commits into
mainfrom
fix/quota-readonly-20261109
Nov 10, 2026
Merged

maxthomas95 merged 2 commits into
mainfrom
fix/quota-readonly-20261109

Conversation

@maxthomas95

Copy link
Copy Markdown
Owner

Summary

Review section 2 (docs/REVIEW_2026-11-09.md).

The quota service refreshed Claude Code's OAuth token itself and wrote the result back into ~/.claude/.credentials.json. It was on by default, undocumented, uncoordinated with Claude Code's own refresh, and retried a rejected refresh every five minutes with no backoff.

  • Read-only. Tether reads the saved access token and sends it only to the usage endpoint. The refresh request, client id, token endpoint and every write to the credentials path are gone. The refresh token is no longer read into memory.
  • Expired token. No request is made. The footer keeps the last observation and shows quota as paused until Claude Code refreshes its sign-in.
  • Rejected token. A 401/403 on a non-expired token backs off per token, 5 to 60 minutes. A successful response, or a different token appearing in the file, resets it at once.
  • Docs. usage-quota.md and settings.md now say how Claude quota is obtained and that the endpoint is not a documented API.

Codex quota, the polling interval and the public service API are unchanged.

Test plan

  • npx tsc --noEmit
  • npx eslint src/main/quota
  • npx vitest run src/main/quota — 12 passed
  • Manual: with a valid Claude sign-in, the quota footer still shows 5-hour and weekly usage
  • Manual: leave Tether open past the token's expiry with no Claude session running — the footer shows "paused", and recovers within a poll after the next Claude Code launch
  • Manual: ~/.claude/.credentials.json modification time does not change while only Tether is running

Out of scope

  • Replacing the undocumented usage endpoint with a supported source.
  • A macOS path (credentials live in Keychain there).

🤖 Generated with Claude Code

maxthomas95 and others added 2 commits November 9, 2026 20:39
The quota service read ~/.claude/.credentials.json and, when the access
token was within five minutes of expiry, posted Claude Code's refresh
token to the OAuth token endpoint and wrote the new tokens back into
Claude Code's credentials file. It did this on by default, with no
coordination with Claude Code's own refresh and no backoff: one install's
logs showed 855 rejected refresh attempts, up to about 270 in a day.

Tether now only reads the access token:
- An expired token makes no request. The footer keeps the last
  observation and reports quota as paused until Claude Code refreshes
  its sign-in.
- A 401/403 on a non-expired token backs off per token (5 to 60
  minutes). A successful response or a new token in the file resets it.
- The refresh request, the client id, the token endpoint and every write
  to the credentials path are removed. The refresh token is no longer
  read into memory.

Docs describe how Claude quota is obtained and that the usage endpoint
is not a documented API.

Tests: expired token makes no request and retains the last reading; a
valid token makes exactly one usage request; no case refreshes or
writes; rejection backoff and reset on token change.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@sonarqubecloud

Copy link
Copy Markdown

@maxthomas95
maxthomas95 merged commit 64d6d0e into main Nov 10, 2026
11 checks passed
@maxthomas95
maxthomas95 deleted the fix/quota-readonly-20261109 branch November 10, 2026 02:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant