At the start of the month I opened the Console usage page and found a metered line that had not been there before. The amount was smaller than a cup of coffee. What stayed with me was that I could not account for it at all.
My Pro invoice arrives separately. So who put that line there?
An environment variable I had set once, months earlier, to try something out — and never removed.
Two ledgers, and Claude Code will run on either
Your claude.ai subscription (Pro, Max, Team, Enterprise) and the pay-as-you-go usage on an API key from the Console are separate ledgers by design. Holding one of them does not make the other free.
Claude Code accepts credentials from both. That is the part that slips past people: finishing a browser login does not, by itself, decide that your subscription is what gets used.
The plan you pay for does not decide the bill. The credential currently selected does.
If what you actually need is help choosing a plan, I keep that separate in the Claude plan cheat sheet for indie developers. Here I want to stay on one narrow problem: the plan you hold not being the plan you use.
One line in status tells you which wallet is open
Type /status at the Claude Code prompt and look at the authentication section for the Login method row. When both a login and an API key are present, Claude Code marks the credential that is not in use, so a single glance settles the question.
If a profile is selected instead — the kind written by ant auth login, or by signing in to a Console account without creating a key — the row is replaced by a Profile row. The row changing its own name is the signal that you came in through a different door.
One more detail worth knowing: if the saved login has expired, that row reads as expired and shows the organization and email address stored alongside it. That row needs Claude Code v2.1.210 or later, and from v2.1.203 you also get a startup warning three days before expiry (five days, on releases before v2.1.217).
To check from the terminal alone, close Claude Code and run one of these:
# macOS / Linux
echo $ANTHROPIC_API_KEY
# Windows PowerShell
echo $env:ANTHROPIC_API_KEY
# Windows Command Prompt
echo %ANTHROPIC_API_KEY%Empty output means the variable is unset. If a string comes back, that key is holding your bill.
Seven levels of precedence, and your login is the last one
When several credentials exist, Claude Code walks the list from the top and stops at the first one it finds.
| Rank | Credential | Typical use |
|---|---|---|
| 1 | Cloud provider (CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY) | Bedrock, Vertex, Foundry |
| 2 | ANTHROPIC_AUTH_TOKEN | Gateways and proxies using bearer auth |
| 3 | ANTHROPIC_API_KEY | Direct API access with a Console key |
| 4 | apiKeyHelper output | Short-lived or rotating credentials |
| 5 | CLAUDE_CODE_OAUTH_TOKEN | CI, or anywhere a browser login is impossible |
| 6 | Profile and federation credentials | The ant CLI, Workload Identity Federation |
| 7 | Subscription OAuth from /login | The default for Pro, Max, Team, Enterprise |
Writing that table out is what finally made it click for me. The subscription is not first — it is last. Leave anything at all above it and your login stays visible on screen while the charges quietly route somewhere else.
In interactive mode you are asked once to approve a key found in your environment, and your answer is remembered. To change it later, use the "Use custom API key" toggle in /config, which only appears while the variable is actually set.
Only the unattended runs were answering differently
This is the part that cost me two days.
As an indie developer I let a cron-invoked shell handle the routine work — updating the Lab sites, sorting source material for my wallpaper apps. That day I opened /status in my own terminal, confirmed it was running on the subscription, and closed it satisfied.
The metered line showed up again the following month.
In non-interactive mode (-p) there is no approval prompt at all. If a key is present, it is used — the "no thanks" I had clicked in an interactive session does not reach there. A shell started by cron has no interactive screen, so the /status I had been reading never once represented what the unattended side was doing.
It took a second invoice before I understood that I had been checking the wrong surface entirely.
Reassurance gathered on an interactive screen does not travel to an unattended run. Let the unattended run identify itself, on its own terms.
A wrapper that records one line before launching gives you the answer by morning:
#!/bin/bash
# Record which credential this run will use, before launching
set -euo pipefail
LOG="${HOME}/claude-runs/$(date +%F).tsv"
mkdir -p "$(dirname "$LOG")"
if [ -n "${CLAUDE_CODE_USE_BEDROCK:-}${CLAUDE_CODE_USE_VERTEX:-}${CLAUDE_CODE_USE_FOUNDRY:-}" ]; then
SRC="cloud-provider"
elif [ -n "${ANTHROPIC_AUTH_TOKEN:-}" ]; then
SRC="auth-token"
elif [ -n "${ANTHROPIC_API_KEY:-}" ]; then
SRC="api-key(metered)"
elif [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]; then
SRC="oauth-token(subscription)"
else
SRC="login(subscription)"
fi
printf '%s\t%s\t%s\n' "$(date -Iseconds)" "$SRC" "$*" >> "$LOG"
claude -p "$*"Running it here leaves this behind:
2026-09-20T15:02:11+09:00 api-key(metered) Tighten the headings in this draftThe branches are ordered to match the precedence table exactly. Reorder them and, on a machine that has both a token and a key, the log drifts away from reality — and a log that disagrees with reality is worse than no log at all.
For the separate problem of unattended runs going missing rather than going expensive, I dug into that from another angle in the reconciliation that caught half my scheduled runs vanishing.
Where the key you deleted comes back from
unset ANTHROPIC_API_KEY only affects the terminal you are standing in. If tomorrow morning is metered again, the key is coming back from somewhere else.
| Where it lives | Scope | How to remove it |
|---|---|---|
~/.zshrc / ~/.bash_profile | Every new shell | Delete the line and re-source the file |
| Windows user environment variables | Every process for that user | Remove it in System Properties, restart the terminal |
The env block of a settings file | Sessions that settings file covers | Delete the key from the settings file |
CLAUDE_CODE_OAUTH_TOKEN | Re-read in every new session | Remove it from your shell profile |
A profile under configs/ | Every surface that reads profiles | /logout or ant auth logout |
CLAUDE_CODE_OAUTH_TOKEN deserves a footnote. It authenticates against your subscription, so in billing terms it does not send you to metered usage. But running /login while it sits in your shell profile switches only the current session; every new session reads the variable again. The confusion feels identical even though the bill does not change.
Surfaces differ too, and knowing that saves some guessing. Claude Desktop and cloud sessions read neither these environment variables nor apiKeyHelper. One machine running metered in the terminal and subscription on the desktop is an ordinary state, not a bug.
What to do today
Open Claude Code once and read a single row: Login method, or Profile if that is what you see. Whichever one is not marked is the account currently receiving your charges.
Adding that one glance to my start-of-month routine is what made opening the Console usage page stop feeling like bad news. Thank you for reading.