Skip to content

fix: verify Anthropic API key against the real API before saving#16

Merged
archdex-art merged 1 commit into
mainfrom
fix/anthropic-key-verification
Jul 17, 2026
Merged

fix: verify Anthropic API key against the real API before saving#16
archdex-art merged 1 commit into
mainfrom
fix/anthropic-key-verification

Conversation

@archdex-art

Copy link
Copy Markdown
Owner

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.

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.
@archdex-art
archdex-art merged commit 73b0118 into main Jul 17, 2026
2 checks passed
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.

1 participant