fix(anthropic): honor CLAUDE_CONFIG_DIR when locating Claude Code credentials - #81252
Open
griffinwork40 wants to merge 1 commit into
Open
fix(anthropic): honor CLAUDE_CONFIG_DIR when locating Claude Code credentials#81252griffinwork40 wants to merge 1 commit into
griffinwork40 wants to merge 1 commit into
Conversation
…dentials agent/anthropic_adapter.py read and wrote Claude Code's OAuth credentials at a hardcoded ~/.claude/.credentials.json, ignoring CLAUDE_CONFIG_DIR. Per Anthropic's docs (https://code.claude.com/docs/en/authentication): "If you've set the CLAUDE_CONFIG_DIR environment variable on Linux or Windows, the .credentials.json file lives under that directory instead." Impact: a Linux/Windows user who sets CLAUDE_CONFIG_DIR was silently not found by hermes at the read site, which then fell through to the ANTHROPIC_API_KEY branch of resolve_anthropic_token() — an unannounced switch from subscription/plan billing (OAuth Bearer) to prepaid API credits (x-api-key). The write-back site was worse: on refresh it wrote a stale duplicate credentials file to the default path that Claude Code itself never reads. Add a single _claude_code_credentials_path() helper and use it at both call sites (_read_claude_code_credentials_from_file, _write_claude_code_credentials). It resolves CLAUDE_CONFIG_DIR at call time (never cached), treats an empty/whitespace-only value as unset (matching the os.getenv(...).strip() style already used in resolve_anthropic_token()), and expands ~ / $VAR via os.path.expanduser + os.path.expandvars. Unconditional on platform: unset behaves identically to before (strictly additive), and a Darwin/Windows gate would only add a branch with no behavioral benefit. Independent of and does not conflict with NousResearch#75146, which only touches _read_claude_code_credentials_from_keychain (the macOS Keychain path); this change touches only the file read and write-back path.
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.
Symptom
A Linux/Windows user who sets
CLAUDE_CONFIG_DIRto relocate ClaudeCode's config directory gets silently switched from subscription/plan
billing to prepaid API credits when using hermes-agent as an Anthropic
provider.
agent/anthropic_adapter.pylooked for Claude Code's OAuthcredentials at a hardcoded
~/.claude/.credentials.json, never checkingCLAUDE_CONFIG_DIR. When that file isn't found at the default path,resolve_anthropic_token()falls through its priority chain all the wayto the
ANTHROPIC_API_KEYbranch — trading Bearer-auth OAuth (planallowance) for
x-api-keyauth (metered credits), with all of thediagnostic breadcrumbs at
logger.debug. The credential refreshwrite-back path had a second, worse instance of the same bug: on
refresh it wrote a stale duplicate credentials file to the default path
— a file Claude Code itself never reads.
Docs justification
Per Anthropic's official Claude Code docs
(https://code.claude.com/docs/en/authentication):
CLAUDE_CONFIG_DIRwas honored nowhere in this repo prior to this PR(repo-wide grep returned 0 hits).
Fix
Added one module-level helper,
_claude_code_credentials_path(), andused it at both call sites that previously inlined the hardcoded path:
_read_claude_code_credentials_from_file()_write_claude_code_credentials()Behavior:
CLAUDE_CONFIG_DIRis set and non-empty after.strip(), resolvesto
<that dir>/.credentials.json.os.getenv(...).strip()convention already used inresolve_anthropic_token()).~and$VARviaos.path.expanduser+os.path.expandvars(covers
CLAUDE_CONFIG_DIR=~/foo).processes that set/change the env var after import pick it up.
Platform-unconditional by design (no Darwin/Windows gate): when the
var is unset, the helper's output is byte-identical to the prior
hardcoded expression, so this is strictly additive. A platform check
would only add a branch with no behavioral benefit — macOS users who
never set
CLAUDE_CONFIG_DIRget the same default path either way.Relationship to #75146
Independent of, and does not conflict with, #75146 — that PR only
touches
_read_claude_code_credentials_from_keychain(the macOSKeychain path). This PR touches only the file read
(
_read_claude_code_credentials_from_file) and the write-back(
_write_claude_code_credentials). Neither function nor line rangeoverlaps.
Testing
New file
tests/agent/test_anthropic_claude_config_dir.py(14 tests,real imports, no mocking of the module under test,
tmp_path+monkeypatch, invariant-based assertions):CLAUDE_CONFIG_DIR~and$VARexpansion in the configured valueCLAUDE_CONFIG_DIRlocation, including awrite → read round trip through the real read path
under
CLAUDE_CONFIG_DIRVerification
Confirmed the worktree's interpreter imports the worktree's own code
(not the main checkout) via
python -c "import agent.anthropic_adapter as m; print(m.__file__)"before running any gate.Scope
Minimal and surgical — one new helper, two call sites updated to use it,
no changes to the atomic-write / 0600 /
os.replacelogic in_write_claude_code_credentials, no refactor of the surrounding file.