Before running autonomous tasks, you need to understand a simple but non-obvious thing: who exactly pays for the work. Claude Code allows several independent authentication routes, and the same interface can sit atop entirely different billing. A Claude Pro or Max subscription is counted against subscription limits, Team and Enterprise against the organization's seat quota, Claude Console as API billing, and Amazon Bedrock, Google and other providers against your cloud account. The answers look the same, the bill arrives differently.
The first login is simple: the claude command opens a browser login. If the browser callback is unavailable - over SSH, in WSL2 or a container - Claude Code shows a login code to paste into the terminal. Login can be managed from the shell too: claude auth login (including with the console flag), claude auth status and claude auth logout. An important detail for scripts: claude auth status exits with code 0 when logged in and 1 when not, so a bootstrap need not parse human text.
The ANTHROPIC_API_KEY variable needs separate understanding, because it silently changes the source of billing. If the key is present in the environment, non-interactive claude -p uses exactly it. In interactive mode Claude Code asks once to confirm that the key will replace subscription authentication. If you meant to work on a subscription while a key lurks in the environment, you will unexpectedly go to API billing - so the key's presence is worth checking before a run.
The key must be checked carefully, without printing its value into a log. It is enough to confirm the variable's presence rather than output its contents: one line with test -n answers "the key is set" or "not set" without revealing the secret. This is a small thing, but hygiene is built of exactly such small things: a credential's value must not end up in the shell history, CI logs or the session transcript, from which it is later hard to scrub.
It helps to gather the login-management commands and the safe key check side by side once. Below is login and status from the shell, and a check for the presence of ANTHROPIC_API_KEY without revealing its value. You return to this map when setting up a new environment or debugging "why is it billed to the wrong place": first you find out the actual authentication route, and only then the model and the task.
The key warning: the same UI does not mean the same bill. The model and answers may look identical, but a subscription login, a Console API key, Bedrock and other providers are counted and limited differently - by different limits, different policies, different billing consoles. Before expensive automation it is worth checking /status, /usage and the specific provider's billing console rather than relying on it "looking as usual".
Working with secrets follows a few firm rules. Keys are not placed in CLAUDE.md, .claude/settings.json, .mcp.json or workflow YAML. A project .mcp.json must reference a variable of the form ${NAME} rather than contain the secret itself. GitHub Actions stores them in repository or organization secrets, GitLab in masked CI/CD variables. For short-lived credentials you use apiKeyHelper, cloud-provider federation or a gateway, and in the sandbox you separately protect credential files and environment variables.
The typical authentication failures are predictable and costly. An ANTHROPIC_API_KEY forgotten in the environment, because of which an autonomous run goes to API billing instead of the subscription. A secret committed into settings or .mcp.json and settled in git history forever. Expensive automation launched without checking which provider and which limit it is billed against. First find out the billing route via /status and the login status, keep secrets out of the repository - and only then release the agent into autonomous work.
# Check the key's presence WITHOUT printing the value
test -n "$ANTHROPIC_API_KEY" && echo "API key is set" || echo "API key is not set"
# If the key is set, claude -p goes to API billing, not the subscription# Managing login from the shell
claude auth login # browser login (or a login code over SSH/WSL2)
claude auth login --console # the Console route
claude auth status # exit 0 = logged in, 1 = not (handy for scripts)
claude auth logout