Skip to content

Subscription poller keeps using stale in-memory OAuth token after Claude Code refresh (poll fails with 401) #3913

Description

@GUTTERTRASH

Version: headroom-ai 0.39.1

Symptom: /stats → subscription_window shows growing poll_errors and last_error: "fetch returned None"; polled_at goes stale for hours (our dashboard: "allowance reading is 3h 30m old"). The proxy itself is healthy.

Cause: SubscriptionTracker._maybe_poll (subscription/tracker.py) uses self._current_token (Bearer token captured from the last proxied request) and only falls back to read_cached_oauth_token() when that is empty (token = token or bg_token). Once Claude Code refreshes its OAuth token and writes the new one to ~/.claude/.credentials.json, the in-memory token is rejected with 401. SubscriptionClient.fetch turns this into None, and the tracker never retries with the fresh file token. If no requests flow through the proxy, _current_token is never replaced.

Verified: calling https://api.anthropic.com/api/oauth/usage with the credentials-file token returned 200 while the poller kept failing.

Suggested fix: prefer the credentials-file token (or retry with it after a 401/None), falling back to the in-memory token; optionally clear _current_token on 401. Our local patch:

file_token = read_cached_oauth_token()
snapshot = None
if file_token:
    snapshot = await self._client.fetch(file_token)
if snapshot is None and token and token != file_token:
    snapshot = await self._client.fetch(token)

After this, poll_errors stayed at 0 following a restart.

Activity

  1. added a commit that references this issue on Oct 5, 2026
    2dc9fc2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions