fix: respect CLAUDE_CONFIG_DIR for credential lookup#59
fix: respect CLAUDE_CONFIG_DIR for credential lookup#59that-lucas wants to merge 5 commits intogriffinmartin:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates Claude credential discovery so OpenCode can locate credentials when Claude is configured to use a non-default configuration directory via CLAUDE_CONFIG_DIR, including macOS Keychain service name derivation.
Changes:
- Derive the credential file path from
CLAUDE_CONFIG_DIRinstead of always using~/.claude. - Derive the macOS Keychain service name suffix from the configured Claude directory (hash-based) to match Claude’s hashed service naming.
- Factor credential path/service-name derivation into helper functions for reuse.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
I can take a look at this in the morning. TY for the contribution. |
griffinmartin
left a comment
There was a problem hiding this comment.
Review
Two items worth addressing before merge:
1. Hash computation may silently diverge from Claude Code's scheme
keychain.ts:33-36 — The implementation hashes the NFC-normalized config dir string, but doesn't resolve the path (trailing slashes, symlinks, .. segments). If a user sets CLAUDE_CONFIG_DIR=/foo/bar/ (trailing slash) and Claude Code strips it before hashing, the service names won't match and keychain lookup silently fails — which is exactly the class of bug this PR is fixing.
Worth verifying against Claude Code's source whether it does any path resolution (e.g., path.resolve(), realpath(), trailing-slash stripping) before hashing.
2. Tests don't actually verify NFC normalization
keychain.test.ts:100 — The hash assertion test computes its expected hash from dir (raw, un-normalized), while the implementation hashes getClaudeConfigDir() (NFC-normalized). For ASCII test paths these are identical, so the test is tautological — it would still pass if NFC normalization were accidentally removed. A test with a path containing combining characters (e.g., "café" as \u0063\u0061\u0066\u0065\u0301 vs NFC \u0063\u0061\u0066\u00e9) would actually exercise that code path.
Addressed in 547754c. For the first point, I investigated against local Claude Code 2.1.80 and the current keychain entry format. The installed binary appears to derive the config dir as For the second point, I added a regression test in src/keychain.test.ts that uses a decomposed Unicode path (cafe\u0301) and asserts the service hash is derived from the NFC-normalized form, so the normalization path is now actually covered. Let me know if that's enough. |
|
@that-lucas is this still relevant with the new keychain support for multiple accounts? |
@griffinmartin, it's unrelated. This is to get your plugin working for people that use the A simple use of that is to get all your tools (think Codex, OpenCode, Claude Code) to use the same home dir to share skills across all tools. |
Summary
CLAUDE_CONFIG_DIRinstead of always assuming~/.claudeIssue
The plugin currently assumes Claude Code always stores credentials in the legacy default locations:
Claude Code-credentials~/.claude/.credentials.jsonThat breaks when Claude is configured with a custom
CLAUDE_CONFIG_DIR.In that setup, Claude derives both credential locations from the configured directory instead:
<CLAUDE_CONFIG_DIR>/.credentials.jsonClaude Code-credentials-<sha256(configDir).slice(0,8)>Because the plugin only checks the old defaults, it can fail to find valid credentials even when
claudeitself is fully authenticated. In practice, this shows up as:security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.opencode-claude-auth: No Claude Code credentials found. Plugin disabled.Solution
Update credential lookup to follow Claude Code's actual storage rules:
process.env.CLAUDE_CONFIG_DIR ?? ~/.claudeCLAUDE_CONFIG_DIRis set~/.claudeand the un-hashedClaude Code-credentialsserviceThis keeps the change narrowly scoped while making the plugin work for both default and relocated Claude config directories.
Verification
npm testnpm run buildnode --input-type=module --experimental-strip-types -e "import { readClaudeCredentials } from './src/keychain.ts'; const creds = readClaudeCredentials(); console.log(JSON.stringify({ found: Boolean(creds), expiresAtType: creds ? typeof creds.expiresAt : null }))"