Missing API key · Run /login
Nothing was sent. No request left your machine, no server rejected anything, and no credential was found to be bad — Claude Code looked through the places it knows about, came up empty, and stopped before making a call. That rules out an expired key, a revoked key, a wrong organization, a rate limit, and every vendor-side fault, because all of those require a request that actually happened.
This is a resolution failure, and the useful question is not “is my key valid” but “which process, running as which user, in which environment, is doing the looking”.
Why this is not the same error as an invalid key
The distinction sounds pedantic until it changes what you do. When a credential is present and refused, the fix lives at the credential: replace it, unset the one that is shadowing it, point at a different endpoint. When nothing resolves at all, the credential is usually fine and sitting exactly where you left it — the process asking for it just cannot see it.
So the entire diagnostic set is different. Regenerating a key cannot fix this error, because there is no key in play to invalidate. Neither can switching organizations, raising a limit, or waiting. The failing thing is the lookup.
Invalid API key · Please run /login
is the other side of that line, and its central case — a stray
ANTHROPIC_API_KEY in your environment quietly winning over your subscription —
is structurally impossible here. An environment variable that is set is the
opposite of a credential that is missing.
The advice in the message is not always followable
/login is a slash command. Slash commands exist inside an interactive
session, and a large share of the situations that produce this error have no
interactive session to type it into: a claude -p run, a CI job, an Agent SDK
application, a hook, a scheduled task, a container entrypoint, a service
running as a different account.
In those contexts the error prints an instruction you cannot follow, which is
why it reads as a dead end. The fix is never to find a way to run /login
there; it is to supply the credential through a channel that context can
actually consume, or to make that context inherit the environment where the
credential already lives.
A related asymmetry is worth knowing: the docs note that /usage-credits is
not available with API key authentication. Claude Code’s auth modes are not
interchangeable skins over the same thing — they differ in what the session can
do — so “just use whichever one works” is not free advice.
Telling the causes apart
Each of these has an observation that confirms or kills it. Run them in order; the first one that matches is yours.
An interactive claude in the same directory works, but your script does
not. The environment differs, not the credential. Compare env output
between the two contexts rather than guessing — a login shell reads profile
files that a CI runner, a cron entry, and a GUI-launched process do not.
It fails under sudo, in a container, as a service account, or on a build
agent. You are a different user, so ~ is a different home. Claude Code
keeps global app state and OAuth in ~/.claude.json, and on Windows ~ means
%USERPROFILE%. A credential written by your desktop account is not visible to
a service account, and no amount of re-logging-in as yourself will change that.
It started after someone set CLAUDE_CONFIG_DIR. That variable relocates
Claude Code’s configuration. Set in one shell and not another, or pointed at a
fresh empty directory, it produces a perfectly working install that has never
seen a credential. Confirm the variable has the same value in the shell where
you logged in and the shell that fails.
An administrator was supposed to provision it and says they did. Managed
settings live in a system directory that differs per OS: macOS
/Library/Application Support/ClaudeCode/managed-settings.json, Linux and WSL
/etc/claude-code/managed-settings.json, Windows
C:\Program Files\ClaudeCode\managed-settings.json. The legacy Windows path
C:\ProgramData\ClaudeCode\managed-settings.json is not read at all — a file
placed there is silently ignored, which looks from the user’s side exactly like
a missing credential. This one is worth checking early on any managed Windows
fleet, because the admin’s evidence and the user’s evidence both look correct.
A credential helper is configured and returns nothing. If your setup shells out to a helper for the credential, a helper that exits non-zero or prints an empty string resolves to no credential. Run it by hand as the failing user and look at what it actually produces.
A credential resolves after you fix the above, and the endpoint then refuses it. That is progress, not a regression — you have moved to a different error. A 401 saying OAuth is not supported means the context now has a credential and needs a different kind of one, which is common when a script targets a gateway or a cloud deployment.
It appeared after you installed a plugin, an MCP server, or a hook. Start
with claude --safe-mode, which launches with all plugins, MCP servers, and
hooks disabled. If the error disappears, a customization is mutating the
environment; if it persists, stop suspecting them.
Is retrying useful?
No — and unlike a flaky network, there is not even a small chance it helps.
A lookup that found nothing will find nothing again a second later. There is no
backoff, no capacity window, and no retry-after to honor, because the server
was never involved in the decision. If a wrapper around your script retries
this error, it is burning wall-clock time on a verdict that was already final
and hiding the real message under a pile of identical ones.
Re-running is only informative as a controlled experiment: run once as yourself in an interactive terminal, and once in the failing context, and compare. The difference between those two runs is the answer.
Fix by scenario
- Non-interactive run, credential exists interactively — pass the credential explicitly into that context rather than relying on inheritance. CI runners, containers and service managers start from a near-empty environment by design.
- Wrong user or wrong home — log in as the account that will actually run Claude Code, or provision the credential for that account. Verify from that account, not from yours.
CLAUDE_CONFIG_DIRmismatch — make it consistent, then log in once under the value you intend to keep.- Managed settings on Windows — move the file to
C:\Program Files\ClaudeCode\managed-settings.json. The legacy location does not work no matter how correct its contents are. - You cannot get a session at all — run
claude doctorfrom the shell. It is a read-only installation and settings diagnostic that does not need a session to start, which is the specific reason it exists.
A standing rule while you do any of this: do not paste a key into a chat, a web
form, or a third-party validator to “check” it, and do not print its value into
a terminal or a CI log. Test for presence, not content — env | grep -c ANTHROPIC_API_KEY tells you what you need without revealing anything, and CI
logs are archived far longer than people expect.
How to confirm it’s fixed
Run the failing context, not a convenient one. If the error came from a CI
job, the confirmation is a CI job; if it came from a service account, su to
that account first. Inside a session, /status should name the credential
source you provisioned — a working session that resolves a different
credential than you intended means you fixed a symptom and left the real
configuration in place.
Then run it twice, with a fresh process each time. A single success can come from a shell that still holds an environment you exported by hand, and that environment dies with the window.
Related errors
If a credential resolves and gets refused, you are on the other page:
Invalid API key · Please run /login.
If the sign-in itself never completes, the failure is in the exchange rather
than the lookup — a login that dies with fetch failed
never reached a server at all, which is a network story wearing an auth label.
To place your own symptom on that map — sent and refused, never sent, or sent
and never answered — use the AI coding error triage tool.