fix: verify Anthropic API key against the real API before saving#16
Merged
Conversation
Bug report: "Invalid API key" when starting a chat with Claude. ## Investigation (evidence, not assumption) Traced the full key path end-to-end against the live main branch: - settings.ts's storage/retrieval/precedence logic (DB value > env var, trim on write) -- structurally correct, no truncation/corruption found. - assistant.ts's env injection into the Claude Agent SDK's Options -- correct: the resolved key always overrides ANTHROPIC_API_KEY when set. - "Invalid API key" is Anthropic's own literal 401 error text, surfacing verbatim through the SDK -- meaning whatever key reaches Anthropic really is being rejected by Anthropic itself. The actual gap: POST /api/settings/assistant accepted ANY string as anthropicApiKey with zero verification and persisted it immediately. A truncated paste, a revoked/expired key, or another provider's key entirely would all "save successfully" and only surface as a failure deep inside a real chat turn -- with no way to distinguish a CodeGraph bug from a bad credential. ## Fix New `lib/anthropicKeyCheck.ts`: `verifyAnthropicApiKey()` calls Anthropic's real `GET /v1/models` endpoint (same base URL + `anthropic-version` header the official `@anthropic-ai/sdk` client itself uses -- confirmed by reading the installed package's own client.js, not assumed) with the pasted key. A free, side-effect-free metadata call, so verifying costs nothing. Wired into POST /api/settings/assistant: a newly pasted key is verified BEFORE ever being written to the settings table. A confirmed-invalid key (401/403) is rejected with Anthropic's own error message and never persisted. A network failure verifying it (offline, DNS, egress blocked) is explicitly NOT treated as evidence the key is bad -- it still saves, since blocking a legitimate key on our own connectivity issue would be worse than the problem being fixed. Clearing a key (null) skips verification entirely -- no network call needed to delete a row. ## Verification - 8 new tests (`anthropicKeyCheck.test.ts`) mocking Anthropic's 200/401/ 403/429/500 responses and a network failure, plus two tests driving the real route handler end-to-end (confirms a rejected key never reaches the DB, and a clear/null request never calls fetch at all). - Live, UNMOCKED smoke test against a real running dev server: POSTed an obviously-fake key, which triggered a real outbound call to Anthropic's actual API, got a real 401, and confirmed via a follow-up GET that the bad key was never persisted (`anthropicApiKeySavedInDb: false`). - Full suite 224/224, tsc --noEmit clean, lint baseline unchanged, next build clean.
This was referenced Jul 17, 2026
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.
Bug report: "Invalid API key" when starting a chat with Claude.
Investigation (evidence, not assumption)
Traced the full key path end-to-end against the live main branch:
The actual gap: POST /api/settings/assistant accepted ANY string as anthropicApiKey with zero verification and persisted it immediately. A truncated paste, a revoked/expired key, or another provider's key entirely would all "save successfully" and only surface as a failure deep inside a real chat turn -- with no way to distinguish a CodeGraph bug from a bad credential.
Fix
New
lib/anthropicKeyCheck.ts:verifyAnthropicApiKey()calls Anthropic's realGET /v1/modelsendpoint (same base URL +anthropic-versionheader the official@anthropic-ai/sdkclient itself uses -- confirmed by reading the installed package's own client.js, not assumed) with the pasted key. A free, side-effect-free metadata call, so verifying costs nothing.Wired into POST /api/settings/assistant: a newly pasted key is verified BEFORE ever being written to the settings table. A confirmed-invalid key (401/403) is rejected with Anthropic's own error message and never persisted. A network failure verifying it (offline, DNS, egress blocked) is explicitly NOT treated as evidence the key is bad -- it still saves, since blocking a legitimate key on our own connectivity issue would be worse than the problem being fixed. Clearing a key (null) skips verification entirely -- no network call needed to delete a row.
Verification
anthropicKeyCheck.test.ts) mocking Anthropic's 200/401/ 403/429/500 responses and a network failure, plus two tests driving the real route handler end-to-end (confirms a rejected key never reaches the DB, and a clear/null request never calls fetch at all).anthropicApiKeySavedInDb: false).