apiKeyHelper written to fix an unrelated MCP key, silently overriding claude.ai auth

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 28, 2026

TL;DR: Claude Code, trying to fix an unrelated MCP server's auth, wrote apiKeyHelper into project settings and pointed it at that server's third-party API key. apiKeyHelper is Claude Code's own Anthropic credential provider, so every subsequent request authenticated with a non-Anthropic key and failed Invalid API key — with a valid claude.ai OAuth token sitting present and unused. No warning at write time, and the error names neither apiKeyHelper nor the file it lives in.

What happened

Working in a project with an MCP server that takes its own API key, correctly configured as env.CONNECT_API_KEY in .claude/settings.json. A hook using that key wasn't working. Claude Code's next move, verbatim from the transcript:

The MCP server needs the API key configured differently. Let me set up an apiKeyHelper:

followed by an Edit adding, alongside the already-correct env entry:

{
  "apiKeyHelper": "echo '<the MCP server's API key>'",
  "env": {
    "CONNECT_API_KEY": "<the same key>"
  }
}

From the next launch onward, every request failed:

Invalid API key · Fix external API key

/doctor showed the real state, though only as one line at the bottom of the Remote Control block:

✓ claude.ai OAuth token present
✗ claude.ai subscriber auth active
Remote Control requires claude.ai subscription auth. apiKeyHelper is configured,
so this session is using API-key auth — unset it to use Remote Control.

Removing the one line and restarting restored everything.

Why this is a product bug, not user error

The name and description don't scope it. The schema says apiKeyHelper is a "Path to a script that outputs authentication values." It doesn't say whose authentication, doesn't say Anthropic, and doesn't mention that setting it overrides an existing claude.ai login. Read cold, "a place that outputs an API key" is a reasonable thing to reach for when you have an API key that needs configuring — which is exactly the inference the model made.

The write is silent. Adding apiKeyHelper repoints the CLI's own credential source with no confirmation, no warning, and no notice that a working OAuth login is now being bypassed. The next launch just fails.

The error leads away from the cause. Invalid API key · Fix external API key offers a button that prompts for another key — pushing the user further into API-key auth when the fix is to stop using it. Nothing in the message names apiKeyHelper or the file it was written to.

It landed in project settings, so it applied to every session in that directory, not just the one that made the change.

The model was wrong here, but it was wrong in a way the config surface invites, and the failure mode gives the user almost nothing to work with.

Reproduction

  1. In any project, add to .claude/settings.json:

``json
{ "apiKeyHelper": "echo 'not-an-anthropic-key'" }
``

  1. Restart Claude Code. Every request fails Invalid API key · Fix external API key, even with a valid claude.ai login present.
  2. Nothing in the error identifies apiKeyHelper or which settings file it's in. /doctor names it only under Remote Control.

Suggested fixes

Make the error name its source. Invalid API key should say where the key came from — apiKeyHelper in <path>, or ANTHROPIC_API_KEY from the environment. This alone turns a multi-hour dig into a five-second read, and it's the smallest change here.

Warn at write time, at least when the model rather than the user is adding apiKeyHelper or env.ANTHROPIC_API_KEY: "this overrides your claude.ai login for this project."

Sharpen the schema description to "Path to a script that outputs an Anthropic API key. Overrides claude.ai OAuth login." Most of the mistake lives in the current wording.

Consider validating the shape at config load — a value that isn't an Anthropic key could fail with a named reason instead of a generic 401 on every request.

Worth noting the changelog already carries a fix in this family — "Fixed apiKeyHelper script failures being hidden behind a generic 401 after ~10 silent retries; the script's own error is now shown within 3 attempts." Same root problem, one layer down: a helper that produces a bad value is still indistinguishable from any other auth failure.

Environment

  • Claude Code v2.1.161, native install, darwin-arm64
  • Model: Haiku 4.5
  • Auth: claude.ai subscription (OAuth token valid and present throughout)
  • Written via the bundled update-config skill

View original on GitHub ↗