fix(oauth): scope the Keychain lookup to the current user's item - #185
Open
Devin345458 wants to merge 1 commit into
Open
fix(oauth): scope the Keychain lookup to the current user's item#185Devin345458 wants to merge 1 commit into
Devin345458 wants to merge 1 commit into
Conversation
The service name is not unique, so `find-generic-password -s NAME -w` returns whichever item the Keychain yields first. Where Claude Code has left a stray acct="unknown" item holding only mcpOAuth, the import reads that one and reports no credentials while a valid login sits in the item next to it. Ask for the current user's item first, fall back to the service-only lookup, and take the first payload that actually carries a token — which also skips a present-but-blank claudeAiOauth left behind by a logout.
There was a problem hiding this comment.
Pull request overview
Scopes macOS Keychain credential lookup to the current user while preserving service-only fallback behavior.
Changes:
- Adds ordered, token-aware Keychain lookups.
- Exports and injects
readKeychainCredentialsfor testing. - Adds regression coverage for lookup and fallback scenarios.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Summary |
|---|---|
test/import-credentials.test.js |
Tests scoped lookup, fallback, blank credentials, and errors. |
src/oauth.js |
Implements scoped Keychain lookup and token selection. |
Suppressed comments (1)
test/import-credentials.test.js:96
- The
args.includes('-a')branch returnsCREDSfor the scoped lookup, soreadKeychainCredentialsreturns immediately and never executes the service-only{ mcpOAuth: {} }branch. This test therefore does not cover skipping a credential-less first payload, which is the stray-item behavior it is intended to protect. Swap the fixtures so the scoped call returns the no-credential payload and the fallback returnsCREDS.
const exec = async (_bin, args) => ({
stdout: JSON.stringify(args.includes('-a') ? { claudeAiOauth: CREDS } : { mcpOAuth: {} }),
});
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
find-generic-password -s "Claude Code-credentials" -wreturns whichever item the Keychain yields first, and the service name is not unique.On this machine Claude Code has left a stray
acct="unknown"item holding onlymcpOAuthnext to the realacct="<login name>"one.importCredentialsreads the stray item, finds noclaudeAiOauth, and reports no credentials — while a valid login sits in the item beside it.Reproduced against the real Keychain, before and after:
The fix
Ask for the current user's item first, fall back to the existing service-only lookup, and take the first payload that actually carries a token. That also skips a present-but-blank
claudeAiOauthleft behind by a logout.Behaviour is unchanged on machines with a single item: the scoped lookup hits it, and if the account name doesn't match, the fallback is the exact call master makes today.
Tests
readKeychainCredentialsis now exported and takes{ exec, username }, so it can be injected the wayimportCredentialsalready is. Six cases added totest/import-credentials.test.jscovering lookup order, the stray-item case, the blank-token case, fallback, the no-username path, and error propagation.531 tests pass,
eslintclean.