Skip to content

fix: support current Claude Code keychain auth#110

Open
andycostintoma wants to merge 3 commits intogriffinmartin:mainfrom
andycostintoma:fix/claude-code-keychain-auth
Open

fix: support current Claude Code keychain auth#110
andycostintoma wants to merge 3 commits intogriffinmartin:mainfrom
andycostintoma:fix/claude-code-keychain-auth

Conversation

@andycostintoma
Copy link
Copy Markdown

Summary

  • detect both Claude Code-credentials* and Claude Code keychain entries when discovering Claude auth sources
  • support the credential shape I observed on macOS when using Claude Code's "generate an API key" flow, where the Claude Code keychain entry stores a raw managed sk-ant-... key instead of OAuth JSON
  • sync that Claude Code entry into OpenCode as type: \"api\" and add tests covering both keychain formats

Testing

  • node --test --experimental-strip-types src/**/*.test.ts

@griffinmartin
Copy link
Copy Markdown
Owner

Thanks for the PR.
Before we move forward — can you share more about how you ended up with an API key in your keychain instead of OAuth tokens? We haven't seen other users hit this, and the standard OAuth flow has been working reliably. Curious whether there's a specific setup or account type that routes through the "generate an API key" path, or if there's a reason the OAuth flow doesn't work for you.

@andycostintoma
Copy link
Copy Markdown
Author

andycostintoma commented Mar 31, 2026

Thanks for the question! My setup is a bit different from the typical user flow:
I have Claude access through my company, which grants me the ability to create API keys on the fly from claude code (see photo), but I cannot create keys manually through the standard UI/dashboard. Because of this, the OAuth flow doesn't apply to my use case — I'm working with API keys that are already provisioned and stored in my keychain.
Without this PR's change, there was no way for me to use OpenCode with Claude in my setup. This fix allows users like me — who have valid API keys but go through a different provisioning mechanism — to still use OpenCode normally.
Screenshot 2026-03-31 at 20 56 26

@griffinmartin
Copy link
Copy Markdown
Owner

@andycostintoma checks out, just get lint/build to pass and I can get this through.

@andycostintoma
Copy link
Copy Markdown
Author

andycostintoma commented Apr 1, 2026

@griffinmartin done

Copy link
Copy Markdown
Owner

@griffinmartin griffinmartin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, nice work on this — the overall approach is solid and the discriminated union design is the right call. A couple things I'd want addressed before merging though:

Must fix

authType should be required, not optional

Right now ClaudeCredentials.authType is optional (authType?: "api" | "oauth"), but syncToPath and refreshIfNeeded both branch on it. The problem is writeBackCredentials constructs newCreds from creds.accessToken/refreshToken/expiresAt without propagating authType — so any credentials flowing through that path lose their type discriminator and silently fall through to the OAuth branch. Making it required forces all construction sites to be explicit and eliminates a whole class of silent misbehavior.

Guard writeBackCredentials against API key entries

writeBackCredentials is exported but has no check for API key credentials. If it's ever called with an API key source (raw sk-ant-... string), updateCredentialBlob will try to JSON.parse it, fail, and return false silently. I know refreshIfNeeded has the early return so this won't happen through that path today, but the function should protect itself. Something like if (creds.authType === "api") return false at the top would do it.

Other things worth noting

  • The test file mirrors parseCredentials and extractServicesFromDump locally rather than importing the actual exports — works fine now but it's a drift risk over time
  • Number.MAX_SAFE_INTEGER as the expiry sentinel is clever but a comment explaining the intent would help future readers
  • The sk-ant- prefix check is a reasonable heuristic but worth a comment acknowledging the assumption
  • No test coverage for syncToPath with API key credentials — that's the most user-visible behavior change in this PR
  • Minor: authType on the interface vs type in auth.json serve the same purpose but have different names, a cross-reference comment would help

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.

2 participants