Skip to content

fix(anthropic): write scopes field to Claude Code credentials on token refresh#4118

Closed
ticketclosed-wontfix wants to merge 1 commit into
NousResearch:mainfrom
ticketclosed-wontfix:fix/oauth-scopes-credential-write
Closed

fix(anthropic): write scopes field to Claude Code credentials on token refresh#4118
ticketclosed-wontfix wants to merge 1 commit into
NousResearch:mainfrom
ticketclosed-wontfix:fix/oauth-scopes-credential-write

Conversation

@ticketclosed-wontfix

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a compatibility issue where Claude Code reports loggedIn: false and refuses to start when Hermes has refreshed the OAuth token.

Root cause: Claude Code >=2.1.81 added a check that gates on a scopes array containing "user:inference" in ~/.claude/.credentials.json before accepting stored OAuth tokens. When Hermes refreshes the token via _refresh_oauth_token(), it writes only accessToken, refreshToken, and expiresAt — omitting the scopes field. Claude Code's internal auth validator (checkAndRefreshOAuthTokenIfNeeded) returns false immediately when scopes are missing, causing the CLI to report "Not logged in" even though the token is valid and works with the API.

How I found it: Reverse-engineered the minified Claude Code v2.1.87 binary. The function TS(H) checks H?.includes(oh) where oh = "user:inference". This is called from bW8 (checkAndRefreshOAuthTokenIfNeeded) with K.scopes from the credential store. When scopes is undefined, TS() returns false and the auth check bails.

Related Issue

N/A (new discovery — no existing issue)

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/anthropic_adapter.py: Parse scope field from OAuth refresh response and pass to credential writer
  • agent/anthropic_adapter.py: Add scopes keyword argument to _write_claude_code_credentials()
  • agent/anthropic_adapter.py: Persist scopes array in claudeAiOauth credential store; preserve existing scopes when refresh response omits the field

How to Test

  1. Configure Hermes to use the Anthropic provider via Claude Code credentials
  2. Let Hermes refresh an expired token (or trigger manually)
  3. Run claude auth status — should report loggedIn: true (previously reported false)
  4. Run echo "hello" | claude --print — should work (previously failed with "Not logged in")

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q and all tests pass (98 passed in test_anthropic_adapter.py, 2 passed in test_anthropic_oauth_flow.py)
  • I've added tests for my changes — test would require mocking the OAuth token endpoint refresh response with a scope field
  • I've tested on my platform: Ubuntu 24.04 (Proxmox VM)

Documentation & Housekeeping

  • N/A for all documentation items — no config keys or architecture changes

Screenshots / Logs

Before fix:

$ claude auth status
{"loggedIn": false, "authMethod": "none", "apiProvider": "firstParty"}

After fix (adding scopes to credentials file):

$ claude auth status
{"loggedIn": true, "authMethod": "claude.ai", "apiProvider": "firstParty", "email": "...", "orgId": "..."}

…n refresh

Claude Code >=2.1.81 checks for a 'scopes' array containing 'user:inference'
in ~/.claude/.credentials.json before accepting stored OAuth tokens as valid.

When Hermes refreshes the token, it writes only accessToken, refreshToken, and
expiresAt — omitting the scopes field. This causes Claude Code to report
'loggedIn: false' and refuse to start, even though the token is valid.

This commit:
- Parses the 'scope' field from the OAuth refresh response
- Passes it to _write_claude_code_credentials() as a keyword argument
- Persists the scopes array in the claudeAiOauth credential store
- Preserves existing scopes when the refresh response omits the field

Tested against Claude Code v2.1.87 on Linux — auth status correctly reports
loggedIn: true and claude --print works after this fix.
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