Claude Code is Anthropic’s coding agent for the terminal. You start it inside a project folder, tell it what you want in plain English, and it reads files, edits them and runs commands on your machine, asking first unless you tell it not to. It is not open source: the license file in Anthropic’s repository reads “All rights reserved” and points to the Commercial Terms of Service. It ships fast. On 2 November 2026 the newest build was 2.1.316, and the build marked “stable” was 2.1.309 (both numbers come from Anthropic’s release server, which returns the latest version as plain text), a week or so behind.
This is document 1 of 8. In it I go over how to install Claude Code on Linux, macOS and Windows, how to pin the version so your steps keep working, how to log in and check that the login is real, and how to choose a permission mode before the first prompt. My position: use the native installer, pin the version while you are learning and switch off the background updater so the pin holds, and do not trust claude auth status to tell you your key works. Every output below was run for real by the lab behind this guide, on Claude Code 2.1.316 unless a block is labelled with another version. Anything the lab could not run says “not tested” and gives the reason.
What do you need before you install Claude Code?
A terminal, git, and a supported machine. The setup page in Anthropic’s docs lists macOS 13.0 or later, Windows 10 version 1809 or later (or Windows Server 2019), Ubuntu 20.04 or later, Debian 10 or later and Alpine Linux 3.19 or later, with 4 GB of RAM or more and an internet connection. You also need an Anthropic account of some kind, which is the next section’s problem, and a country on Anthropic’s supported list.
That is Anthropic’s list. These runs happened on Debian 12 on x86-64, with Node 22 and git 2.39, in a clean shell with an empty home folder, so nothing left over from earlier installs could help. The lab could not run anything on macOS or Windows, and where that matters the text says so.
Which way should you install Claude Code?
To install Claude Code, use the native installer. Anthropic marks it as the recommended one, it has no Node dependency, and the docs say native installs update themselves in the background (Homebrew and WinGet installs do not). The command is one line:
curl -fsSL https://claude.ai/install.sh | bash
Here is what it printed, in an empty home folder, on a machine whose shell has not been told about the install folder yet:
Setting up Claude Code...
Checking installation status...
Installing Claude Code native build latest...
Setting up launcher and shell integration...
⚠ Setup notes:
● Native installation exists but ~/.local/bin is not in your PATH. Run:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
✔ Claude Code successfully installed!
Version: 2.1.316
Location: ~/.local/bin/claude
Next: Run claude --help to get started
⚠ Setup notes:
● Native installation exists but ~/.local/bin is not in your PATH. Run:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
✅ Installation complete!
Two things in that output are worth reading twice. The installer says native build latest, so you get whatever is latest the minute you run it. That was 2.1.316 on 2 November. On 4 November the plain installer gave 2.1.318, which is the point of the next section. And it did not touch the shell config. The lab had planted a .bashrc and a .zshrc before the run and compared them afterwards: no change. The PATH line is a request to you, and until you do it, claude is not found:
bash: claude: command not found
That is what a terminal you type into prints, and the exit code is 127. A script, or bash -c, prints bash: line 1: claude: command not found for the same cause. The lab ran this on 2.1.316 with a PATH that held no other copy of claude. If an older copy sits somewhere else on your PATH, you will run that one and see no error at all, which is a different problem.
Run the echo line the installer printed, open a new terminal, and check:
claude --version
2.1.316 (Claude Code)
That is the 2 November run. If you run the plain installer today, expect whatever latest is now, 2.1.318 on 4 November, and do not worry about the difference: the next section pins you to 2.1.316 whatever the plain one gave you.
The install took about 16 seconds on this machine. It leaves a symlink at ~/.local/bin/claude, the binary itself at ~/.local/share/claude/versions/2.1.316 (about 313 MiB for this version), a lock folder, a cache folder and a ~/.claude folder for your settings and sessions. If the installer prints nothing for a long time, that is normal: the docs say it shows no progress while it downloads.
How do you pin the version?
Put the version number after bash -s:
curl -fsSL https://claude.ai/install.sh | bash -s 2.1.316
I want you to do this while you work through these documents. Without it, the document you are reading and the program on your machine drift apart, and the first time a step fails you will not know whether you did something wrong or the tool moved. The installer also accepts stable or latest in the same slot, which pins you to a channel and not to a number. A number on the installer line is only where you start, though. The section after the trap below shows what moved a pinned install, and the one setting that stops it.
The pin matters more than it sounds. The one-liner in the docs installs latest. When the lab started on this guide, the sandbox already had 2.1.312 installed, and by the time the install runs happened the same one-liner gave 2.1.316. The npm registry dates 2.1.312 to 27 October and 2.1.316 to 31 October, which is four days, in a tool that ships almost daily.
Here is the pin on a home folder that had never held Claude Code, in a shell that sets SHELL the way a terminal does, so the PATH hint names ~/.bashrc:
Setting up Claude Code...
Checking installation status...
Installing Claude Code native build 2.1.316...
Setting up launcher and shell integration...
⚠ Setup notes:
● Native installation exists but ~/.local/bin is not in your PATH. Run:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
✔ Claude Code successfully installed!
Version: 2.1.316
Location: ~/.local/bin/claude
The second copy of the PATH warning and the closing line are cut here. They match the plain run. Compare it with the plain run: the line that said native build latest now names a number. Once you have added the PATH line, the ⚠ Setup notes block is gone from both.
Now a quirk that bit the lab once, and which you should know about even though it did not come back. The banner is not proof of what you are running. On 2 November, in a home where 2.1.316 was already on disk, the lab pinned 2.1.309 and then pinned 2.1.316 again. The second pin printed a clean banner ending Version: 2.1.316, and yet claude --version answered 2.1.309 (Claude Code) and the symlink at ~/.local/bin/claude still pointed at versions/2.1.309. claude install 2.1.316 moved it. On 4 November the lab ran the same three lines again, in two clean home folders, each with 2.1.316 already on disk:
curl -fsSL https://claude.ai/install.sh | bash -s 2.1.309
curl -fsSL https://claude.ai/install.sh | bash -s 2.1.316
claude --version
Both times the last line printed this, and the symlink pointed at versions/2.1.316:
2.1.316 (Claude Code)
A walkthrough by someone who did not write this document got the same answer in four of four runs. So on the installer as it is today the trap did not reproduce, and a reader following this page will see the second pin work. The installer is a script that Anthropic serves from a fixed address, and the address does not carry a version number, so the lab cannot say what changed or when. The banner was also inconsistent on 2 November: the line Installing Claude Code native build 2.1.316... was there in one run and missing in two of four repeats. The advice survives either way. After every installer run, whatever you put after bash -s, including nothing, run claude --version and believe it over the banner. If it prints a number you did not ask for, claude install 2.1.316 moves the symlink to the version you want.
Does the pin stay pinned?
No, not by itself. When you install Claude Code with a version number, the number on the installer line chooses the version you start on, and nothing more. Anthropic’s setup page says “Native installations automatically update in the background”, and that Claude Code “checks for updates on startup and periodically while running”. The lab tested what that does to a pin. It pinned 2.1.309 in a clean home folder, when 2.1.316 was the newest build, and left the settings alone. claude doctor read:
Running: native (2.1.309)
Path: /tmp/lab/n/hd/.local/share/claude/versions/2.1.309
Auto-updates: enabled
Auto-update channel: latest
Last update attempt: none recorded
Then it started one interactive session in a clone of the companion repository, signed in with the subscription token, typed nothing, and left it at the main prompt. At the first look, 60 seconds in, the footer under the prompt had grown a message on the right:
⏵⏵ auto mode on (shift+tab to cycle) · install gh for PR status ✔ Update installed · /restart to apply
It was still there at 120 and 180 seconds. The header at the top of the screen still said Claude Code v2.1.309, because an update takes effect the next time you start. After /exit, the lab looked at the disk:
/tmp/lab/n/hd/.local/share/claude/versions/2.1.316
2.1.309
2.1.316
2.1.316 (Claude Code)
Running: native (2.1.316)
Path: /tmp/lab/n/hd/.local/share/claude/versions/2.1.316
Auto-updates: enabled
Auto-update channel: latest
Last update attempt: success → 2.1.316 (2026-11-02)
That is the symlink target, the two version files on disk, claude --version, and then the doctor lines. The pin had moved to 2.1.316 without the lab typing an update command. The first look was at 60 seconds, so “within about a minute” is an upper bound and not a measurement. This was one run.
The setting that stopped it is DISABLE_AUTOUPDATER, in the env block of ~/.claude/settings.json. Create the file with this, or add the env key to what is already in it:
{
"env": {
"DISABLE_AUTOUPDATER": "1"
}
}
The lab wrote that file before the first launch, in another clean home with the same pin. claude doctor then read Auto-updates: disabled (set by env: DISABLE_AUTOUPDATER). Same session, same 180 seconds of waiting: no message in the footer, the symlink stayed on versions/2.1.309, only 2.1.309 was on disk, and claude --version printed 2.1.309 (Claude Code). That is also one run, and it shows an absence over three minutes, so read it as “in three minutes of waiting nothing moved” and not as “never”. The docs say the variable “only stops the background check; claude update and claude install still work”, and the update section below shows the lab confirming that.
So the pin is two things: the version on the installer line, and this setting. Not tested: the /restart command the footer names, a background update on a machine signed in with an API key (both runs used the subscription token), and anything finer than the 60-second look.
What if you would rather use npm?
You can. The package is @anthropic-ai/claude-code, and Anthropic’s docs say it needs Node.js 22 or later. The lab ran npm 10.9.9 on Node v22.23.4 and did not try an older Node, so what happens there is not tested. On a Linux box where Node came from a system package, npm’s global folder belongs to root, and the plain command fails:
npm install -g @anthropic-ai/claude-code@2.1.316
npm error code EACCES
npm error syscall mkdir
npm error path /tmp/lab/s/ro-prefix/lib/node_modules
npm error errno -13
npm error Error: EACCES: permission denied, mkdir '/tmp/lab/s/ro-prefix/lib/node_modules'
The lab got that from a root-owned prefix folder standing in for a system one, so it is a simulation of a distro Node and not a real one. If you rebuild it from a read-only empty folder, npm names .../lib and not .../lib/node_modules. Yours will name something like /usr/lib/node_modules. The real stack trace follows these lines, and the lab cut it. The exit code was 243. Same advice whichever path it names.
Do not fix this with sudo. The docs say not to, because Claude Code updates itself with npm and a sudo install cannot do that. Move npm’s global folder into your home instead:
npm config set prefix ~/.npm-global
npm install -g @anthropic-ai/claude-code@2.1.316
added 2 packages in 5s
Those two commands are right for npm 10.9.9, the version the lab used for the blocks above. Run npm --version first. If it starts with 12 and you want to install Claude Code through npm, there is one more step, and it goes before the install. The lab ran the same two commands on npm 12.2.1 (Node v22.23.4, a clean home folder). The install exits 0 and prints the same added 2 packages in 5s, then this:
npm warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts @anthropic-ai/claude-code@2.1.316 (postinstall: node install.cjs)
npm warn install-scripts
npm warn install-scripts Run `npm install -g --allow-scripts=@anthropic-ai/claude-code` to allow these scripts once, or `npm config set allow-scripts=@anthropic-ai/claude-code --location=user` to allow them for all global installs.
and ~/.npm-global/bin/claude --version, run by its full path, printed this and exited 1:
Error: claude native binary not installed.
A paragraph about --ignore-scripts and --omit=optional followed, which the lab cut. The success banner and the dead binary arrive together, which is why this one is easy to misread as a broken download. npm 12 blocks the package’s postinstall script, and that script is what puts the native binary in place. The fix is on Anthropic’s troubleshooting page: save the permission in your npm config, then install. On npm 12 only, and in the order a reader would follow, from a clean folder:
Start with the first three lines only, and stop after them:
npm --version
npm config set prefix ~/.npm-global
npm config get allow-scripts
12.2.1
Read what npm config get allow-scripts printed before you go on, because the next line replaces the whole list and does not add to it. On a home folder with nothing saved, the lab got one blank line. If yours prints package names, put them inside the same quotes after a comma, for example npm config set "allow-scripts=@anthropic-ai/claude-code,esbuild" --location=user. With esbuild already saved, the lab ran that merged line and then installed both, and both installed with no warning. With the single-package line instead, the next npm install -g esbuild@0.25.1 printed 1 package had install scripts blocked and exit code 0, which is easy to miss. Anthropic’s page says the same thing. On npm 10, npm config get allow-scripts prints undefined and the set line fails with npm error `allow-scripts` is not a valid npm option, which is why this block is for npm 12 only. The install after that error still worked.
Once you have read it, run the rest. If your list was not empty, swap your merged line in for the first line of this block and do not run both, because the second one would replace the first:
npm config set "allow-scripts=@anthropic-ai/claude-code" --location=user
cat ~/.npmrc
npm install -g @anthropic-ai/claude-code@2.1.316
prefix=/tmp/lab/n/hc/.npm-global
allow-scripts=@anthropic-ai/claude-code
added 2 packages in 6s
No warning this time. With ~/.npm-global/bin on the PATH, claude --version printed 2.1.316 (Claude Code), and claude doctor started Running: npm-global (2.1.316) and ended No installation issues found., so the doctor sentence further down holds on npm 12 as well. (If you already ran the install without the setting, run the allow-scripts line and the install again. The lab did that over a broken install and got changed 2 packages in 4s and a working claude.)
Save the setting. Do not use the one-shot flag from npm’s own warning. The lab ran npm install -g --allow-scripts=@anthropic-ai/claude-code @anthropic-ai/claude-code@2.1.316 and it worked, but npm does not remember it. The next npm install -g @anthropic-ai/claude-code@2.1.309 in that home folder printed the same four warnings, said changed 2 packages in 8s, and left claude --version printing the native binary not installed error. A working install was broken by installing another version over it, which is what Anthropic’s page means by the error returning after your next update.
Not tested: npm 12.0.x, where the page says to update npm to 12.1 or later first (the lab only had 12.2.1); the EALLOWSCRIPTS error the page describes for project installs through pnpm 9, pnpm 10 or Yarn 1, which does not touch the global install here; and an update run by npm itself on npm 12. The lab only ran the native updater.
Then put ~/.npm-global/bin on your PATH. Until you do, claude is not found. The lab ran ~/.npm-global/bin/claude --version by its full path and got 2.1.316 (Claude Code). npm ls -g prints two lines, the folder path and └── @anthropic-ai/claude-code@2.1.316. With npm ls -g --depth=1 the lab counted seven lines starting UNMET OPTIONAL DEPENDENCY, one for every platform you are not on, and one line that is not unmet, @anthropic-ai/claude-code-linux-x64@2.1.316. The unmet lines look like a failed install and they are not: the package ships one binary per platform and installs the one that fits.
With no version on the end, the lab’s plain npm install -g @anthropic-ai/claude-code gave 2.1.316. The registry’s tags that day came from this command:
npm view @anthropic-ai/claude-code dist-tags --json
{
"stable": "2.1.309",
"next": "2.1.316",
"latest": "2.1.316"
}
So npm’s latest and the native installer’s latest agree, and npm has a stable tag that matches the stable channel. For your own pin, put @2.1.316 on the end, as above. On an npm install, claude doctor starts with Running: npm-global (2.1.316) and says Config install method: not set.
My view: if you must install Claude Code through npm, do it only when your company already blocks curl | bash, or you want npm’s version pinning in a lockfile. There is nothing in the npm package that the native one lacks, and the native one updates without asking you to own a Node setup.
What about Homebrew, WinGet, apt and Windows?
Not tested, and here is what the lab did instead. There is no Mac and no Windows machine in the lab, and no root on the Linux box, so none of these installs ran.
- macOS and Homebrew: not tested, no Mac. The docs give two casks:
brew install --cask claude-codefollows the stable channel, “typically about a week behind”, andclaude-code@latestfollows latest. The lab read the public Homebrew API instead of installing: the first cask served 2.1.309 and the second 2.1.316 on 2 November. Homebrew installs do not update themselves. - Windows (PowerShell, CMD, WinGet) and WSL: not tested, no Windows host. The docs give
irm https://claude.ai/install.ps1 | iexfor PowerShell, acurl ... install.cmdline for CMD andwinget install Anthropic.ClaudeCode. The lab fetchedinstall.ps1andinstall.cmd, and both answered HTTP 200. That is all it can say. If you are on Windows and have WSL, the Linux steps in this document are the ones the lab ran, so I would start there. Nobody has run them under WSL here, so treat that as a guess. - apt, dnf, apk: not tested as installs, no root. The lab downloaded the stable
.debthe apt repository serves and checked its SHA-256 against the repository index: it matched. The package depends only on libc6 and puts the binary at/usr/bin/claude. The newest version in that stable repo was 2.1.309. - Alpine and musl: not tested, no Alpine machine. The docs list extra packages you have to add first.
- zsh and fish: the installer printed the bash form of the PATH line, because the shell was bash. The lab did not see what it prints for zsh or fish.
Does claude keep itself up to date, and can it go backwards?
It updates itself, and yes, it can go backwards. First the quiet case. On a pinned 2.1.316 install on the default latest channel, the update command, which is claude update, printed:
Current version: 2.1.316
Checking for updates to latest version...
Claude Code is up to date (2.1.316)
That was 2 November, when 2.1.316 was the newest build. On 4 November a newer one existed, and the same command on a pinned 2.1.316 install, with no settings file at all, printed this:
Current version: 2.1.316
Checking for updates to latest version...
Updating to 2.1.318...
Successfully updated from 2.1.316 to version 2.1.318
claude --version then printed 2.1.318 (Claude Code). A pin is not a lock: the update command does what it says, whatever the number on your installer line was, so do not run it while you are following these documents unless you mean to move. If you did, claude install 2.1.316 (the 2.1.316 build was still on disk) put the symlink back and claude --version printed 2.1.316 (Claude Code) again. From 2.1.309 on 2 November it printed Successfully updated from 2.1.309 to version 2.1.316. To move to a version already on disk, claude install 2.1.309 --force moved the symlink back to 2.1.309, and claude install 2.1.316, without --force, moved it forward again.
Now the one that bit. With this one line in ~/.claude/settings.json:
{"autoUpdatesChannel": "stable"}
claude update did this, with no question asked:
Current version: 2.1.316
Checking for updates to stable version...
@stable is at 2.1.311 (older than 2.1.316)...
Successfully updated from 2.1.316 to version 2.1.311
(That is the 4 November run. On 2 November the stable channel was at 2.1.309 and the lines named 2.1.309 in both places. The number moves, and the behaviour does not.)
Update, in that sentence, means downgrade. The stable channel lags, so a machine that picks stable and then runs the update command can lose features it was using. Both version files stayed on disk (ls ~/.local/share/claude/versions listed 2.1.309 and 2.1.316 on 2 November). One more setting stops it, minimumVersion, and it has to be in place before the update:
{"autoUpdatesChannel": "stable", "minimumVersion": "2.1.316"}
Current version: 2.1.316
Checking for updates to stable version...
The stable channel is at 2.1.311, which is below your minimumVersion setting (2.1.316). Staying on 2.1.316.
The order matters. Added after the downgrade has already happened, the setting fixes nothing. The lab’s run on 2 November ended with ... Staying on 2.1.309. and stayed there. claude install 2.1.316 took it back, and the next claude update then said Staying on 2.1.316.
Two more things from the same runs, started from 2.1.309 so that an update had somewhere to go. Setting DISABLE_AUTOUPDATER to 1 in the env block of settings turns off background updates (claude doctor then reads Auto-updates: disabled (set by env: DISABLE_AUTOUPDATER)), but claude update still updated the install, from 2.1.309 to 2.1.316. The variable that blocked the manual command too was DISABLE_UPDATES, which gave Updates are disabled by your administrator. Contact your IT team to get the latest version. and left the version where it was. What the background updater does to a pinned install is in the pin section above, where the lab did run it.
How do you put the settings file back afterwards?
The stable-channel experiments leave ~/.claude/settings.json in a state you do not want to keep. It holds "autoUpdatesChannel": "stable", the DISABLE_AUTOUPDATER line from the pin section is gone if you replaced the file with the one-line examples above, and claude doctor reads Auto-update channel: stable. Put it back, so that the rest of your Claude Code install matches the guide: background updates off, the minimumVersion floor so no update can take you below 2.1.316, and no channel line, which means the default latest. Anything else in the file, such as the theme the first run wrote, stays. If you have jq, one command does it and leaves other keys alone. Here it is run on the file the last example above leaves you with, the one holding the channel line and the floor:
jq 'del(.autoUpdatesChannel) | .env.DISABLE_AUTOUPDATER = "1" | .minimumVersion = "2.1.316"' ~/.claude/settings.json > ~/settings.tmp && mv ~/settings.tmp ~/.claude/settings.json
cat ~/.claude/settings.json
{
"minimumVersion": "2.1.316",
"env": {
"DISABLE_AUTOUPDATER": "1"
}
}
That is the whole file when it held only the lines above before the command. If yours holds more, it still holds it after the command: a file that also had "theme": "dark" and "skipDangerousModePermissionPrompt": true (the second is written when you accept the bypass-permissions warning in a later section) printed both of them too, in the same order, with minimumVersion and env as shown. The lab ran both cases with jq 1.6.
To confirm that the way you install Claude Code still holds the pin, the lab ran claude doctor | grep -E "^(Running|Auto)", which printed:
Running: native (2.1.316)
Auto-updates: disabled (set by env: DISABLE_AUTOUPDATER)
Auto-update channel: latest
Without jq, open the file in an editor and make it the same shape by hand. Two things this file does not do. It does not stop claude update: with this exact file the lab ran the command and it moved from 2.1.316 to 2.1.318, because a floor only blocks going down, so the rule above stands, and claude install 2.1.316 brought it back. And it does not stop you being moved by a plain installer run, which installs latest. The file stops the quiet background move, which is the one you would not notice. Document 3 starts from this file and shows it beside the others.
How do you check the install?
claude doctor
On a clean native install it prints the version, the commit, the platform, where the binary lives, the install method, the update channel, and ends with No installation issues found. It runs without logging in. If an npm copy of Claude Code is also on the machine, it warns about it. That is the case when you install the native build on a box that had the npm one first. On 2.1.316, with a native install and a root-owned npm copy at /usr/local/bin/claude, the lab got this near the end:
Multiple installations found
- npm-global at /usr/local/bin/claude
- native at /tmp/lab/s/h4a/.local/bin/claude
and, after the Remote Control block, this:
1 warning found
- Leftover npm global installation at /usr/local/bin/claude
Fix: Run: npm -g uninstall @anthropic-ai/claude-code
The paths are the lab’s. claude update printed the same warning, and the native build does not remove the npm copy for you, so run the fix line or you will see the warning every time. The lab saw it find the npm copy through npm’s global folder, not through PATH, and with npm pointed at an empty folder there was no warning.
For a reader, the part to look at is the top and the bottom. This is claude doctor on a clean native 2.1.316 install, cut in the middle (the Remote Control block lists what a missing claude.ai login blocks). The two not fetched lines are new in 2.1.316 and say that no login was available for the policy lookups:
Claude Code doctor
Running: native (2.1.316)
Commit: cd7ceb459c68
Platform: linux-x64
Path: /tmp/lab/s/h4c/.local/share/claude/versions/2.1.316
Config install method: native
Search: OK (bundled)
Auto-updates: enabled
Auto-update channel: latest
Last update attempt: none recorded
Managed settings (remote): not fetched — no usable credentials for the settings fetch
Organization policy: not fetched: no API key or claude.ai sign-in for the policy lookup (apiKeyHelper keys are not used for it)
No installation issues found.
For a full setup checkup that can also fix issues, run /doctor in a Claude Code session.
Inside a session the same check is /doctor. The lab ran claude doctor from the shell, and not /doctor.
The binary can also be checked against Anthropic’s signed manifest. The manifest signature verified against the key fingerprint printed in the docs, 31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE, and changing one byte made it fail. The lab did not run the docs’ gpg --import commands as written, because gpg is not installed on its machine. It used gpgv with the key unpacked by hand. If you want the real check, use the docs’ commands and not the lab’s.
How do you log in, and how do you know it worked?
There are four ways to get a working login, and the lab ran two of them. The authentication page lists the account types. A Claude Pro, Max or Ultra subscription logs in with the claude.ai account. Claude for Teams or Enterprise logs in with the account your admin invited you to. A Claude Console account bills by API usage. And a cloud provider (Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry) takes environment variables and no browser login.
What the lab ran: a subscription token and an API key. What it did not run: the browser login for a claude.ai account or a Console account, claude auth login, /login, /logout, claude setup-token, and any cloud provider. All of those need a person’s account or cloud credentials, and the lab had one subscription token and one capped API key. Everything below about the browser flow is the docs’ word, not a run.
My position, for a developer who has a Pro or Max plan already: log in with it. A subscription is a flat monthly fee, an API key is a meter, and a meter is the worse thing to hand to a beginner on day one. The API key is the right choice for scripts and CI, which is where documents 7 and 8 take you. I am not going to quote plan prices here, because they change and the pricing page is the one source that stays right.
On a subscription token, claude auth status printed this, with exit code 0, on 2.1.316:
{
"loggedIn": true,
"authMethod": "oauth_token",
"apiProvider": "firstParty",
"analyticsDisabled": false,
"projectsDirectory": "/tmp/lab/r1/.claude/projects",
"configDirectory": "/tmp/lab/r1/.claude"
}
That token came from claude setup-token, which per the docs makes a one-year OAuth token that needs a Pro, Max, Ultra, Team or Enterprise plan. The command prints the token and saves it nowhere, and you export it as CLAUDE_CODE_OAUTH_TOKEN. The docs say it can only make model requests, so it cannot start Remote Control sessions or fetch claude.ai connectors. That is how the lab ran every example in this guide without a browser. The lab did not run setup-token itself.
With nothing set at all, claude auth status --text says so:
Not logged in. Run claude auth login to authenticate.
and claude -p "reply with the single word: ok" prints Not logged in · Please run /login. Both exit 1, on 2.1.316. Plain claude auth status with nothing set prints the JSON with "loggedIn": false and "authMethod": "none", and also exits 1. The JSON form of the claude -p call reported total_cost_usd as 0 and is_error as true.
Which credential wins when you have several? The docs give an order, and for a beginner only the top of it matters: cloud-provider variables first, then ANTHROPIC_AUTH_TOKEN, then ANTHROPIC_API_KEY, then an apiKeyHelper script, then CLAUDE_CODE_OAUTH_TOKEN, and the subscription login from /login last. So a stray ANTHROPIC_API_KEY in your shell beats your Pro login once you approve it, and the docs warn that this can fail if the key belongs to a disabled organization. unset ANTHROPIC_API_KEY puts you back on the subscription, and /status shows which one is in use. The lab did not test the ordering. It read the order off the page.
The first thing claude prints on a clean machine is a theme picker, with Dark mode preselected, and after that the login choice. (If ANTHROPIC_API_KEY is set, a key question comes between the two, and the API-key paragraphs below cover it.) This is a drawing of the 2.1.316 text, not a terminal screenshot:

Claude Code can be used with your Claude subscription or billed based on API usage through your Console
account.
Select login method:
❯ 1. Claude account with subscription · Pro, Max, Team, or Enterprise
2. Anthropic Console account · API usage billing
3. 3rd-party platform · Amazon Bedrock, Microsoft Foundry, Google Vertex AI
On 2.1.316 the lab saw the same three choices. Choice 1 opens a browser sign-in: the screen reads Browser didn't open? Use the url below to sign in (c to copy) and Paste code here if prompted >. The lab did not follow it, because it needs a person’s account.
Now the check that does not check anything. Put a deliberately fake key in the environment and ask Claude Code about it:
export ANTHROPIC_API_KEY=sk-ant-api03-FAKE-key
claude auth status
{
"loggedIn": true,
"authMethod": "api_key",
"apiProvider": "firstParty",
"analyticsDisabled": false,
"projectsDirectory": "/tmp/lab/s/h7/.claude/projects",
"configDirectory": "/tmp/lab/s/h7/.claude",
"apiKeySource": "ANTHROPIC_API_KEY"
}
loggedIn: true, exit code 0, for a key made of zeros. The command reads the variable and reports that a key is present. It never asks the server whether the key is any good. The first real request does:
claude -p "reply with the single word: ok"
Invalid API key · Fix external API key
Exit code 1, and the JSON form of the same call reported total_cost_usd as 0, api_error_status as 401, and 0 input and 0 output tokens, so a rejected key costs nothing. A fake key shaped like a real one, 98 characters long, gave the same answer. If you are scripting a login check, make it a one-word claude -p call and look at the exit code, not at auth status. These are all 2.1.316 runs.
One more first-run detail, for a reader who holds only an API key. On 2.1.316 a clean machine with ANTHROPIC_API_KEY set shows the theme picker and then, before anything else, this:
Detected a custom API key in your environment
ANTHROPIC_API_KEY: sk-ant-...
Do you want to use this API key?
Yes
❯ No (recommended)
Enter to confirm · Esc to cancel
The real screen prints the last characters of your key after sk-ant-..., and the lab’s capture masks them. Do not screenshot this one without cropping that line. The cursor starts on “No”. Press Enter without reading and you have declined your own key, and the next screen is the login menu from earlier, which wants a browser. The lab saw this with a fake key, with the real capped key and with --model haiku on the command line, so the flag does not change it. Esc on the login menu did nothing. On the key dialog itself, a repeat run found Esc behaves like No and lands on the login menu. Choosing the Console account (option 2) opens a second menu, which is also a browser sign-in:
Anthropic Console account
How do you want to sign in?
❯ 1. Sign in with your Console account (recommended)
2. Create an API key (legacy) · adds a key to your Console workspace
3. Go back
So on a clean machine, press Up and then Enter to answer Yes, and the next screen is not the login menu. The lab did exactly that on 2.1.316: a fresh home folder, only ANTHROPIC_API_KEY set (the real capped key), inside a clone of the tag doc-1-start. After Yes came the security notes (Press Enter to continue…), then the trust prompt from the next section (Down, then Enter), then the main screen, whose header read Opus 5.5 · API Usage Billing. /status there showed:
Auth token: none
API key: ANTHROPIC_API_KEY
Model: Default (claude-opus-5-5)
Setting sources: User settings
Auto mode server: Enabled
A one-word prompt, reply with the single word: ok, was answered ok, and /cost printed Total cost: $0.0330 for it. That was a cold Opus call on the key, the same kind of call as the expensive one in the cost section, so do not read $0.0330 as what a first prompt costs.
Claude Code remembers the answer the moment you give it, not at the end of setup. The lab stopped its first launch at the security notes, so setup was unfinished. The second launch showed the theme picker again, with no key question, and went to the security notes after it. The answer is stored in ~/.claude.json, under customApiKeyResponses: one entry in the approved list, none in the rejected one. If you quit during setup, you may see the theme picker again, but not the key question. The other route that needs no browser is claude -p, which uses the key alone, as the one-word call above showed. In the docs’ words, the key is used without asking in non-interactive mode, so a script behaves differently from your terminal.
What does your first session look like?
Clone the companion repository, a small Node service with 25 tests and no install step. Every document after this one starts from a tagged clone of it. This one starts from the tag doc-1-start, which is commit 3e19f24, the commit this guide was run against. Cloning by tag keeps your screens the same as the ones below even after later documents change the repository.
git clone --branch doc-1-start https://github.com/dominicplouffe/order-notifier.git
cd order-notifier
npm test
claude
git will tell you it is in a detached HEAD state after that clone, which is expected for a tag. npm test prints # tests 25 and # pass 25 among its last eight lines, and # fail 0, # cancelled 0, # skipped 0, # todo 0 and # duration_ms come after them.
Four screens come before the prompt, and the lab drove all of them on 2.1.316 in a clean home folder. The first is the theme picker (Dark mode is preselected, and /theme changes it later). If you hold only an API key, the key question from the previous section comes next. The second is two security notes: Claude can make mistakes, and only use it with code you trust. The third matters more than it looks:
Accessing workspace:
/tmp/lab/r4/order-notifier
Quick safety check: Is this a project you created or one you trust? (Like your own code, a well-known open
source project, or work from your team). If not, take a moment to review what's in this folder first.
Claude Code'll be able to read, edit, and execute files here.
Security guide
❯ No, exit
Yes, I trust this folder
Enter to confirm · Esc to cancel
The cursor starts on “No, exit”. Press Enter without reading and Claude Code quits, which is the safe default and also the thing that makes the first run look broken. Press Down, then Enter, on a folder you trust.
Then the main screen, which on 2.1.316 opens with a notice about the permission mode. The top of it, with the logo trimmed out:
Claude Code v2.1.316
Sonnet 5.5 · Claude API
~/order-notifier
▎ Auto mode is now Claude Code's default permission mode.
▎ Auto mode lets Claude handle permission prompts automatically. Claude checks each tool call for risky
▎ actions and prompt injection before executing, runs the ones it assesses as lower-risk, and blocks the rest.
▎ https://code.claude.com/docs/en/permission-modes
1 more notice hidden
❯ Try "create a util logging.py that..."
⏵⏵ auto mode on (shift+tab to cycle) · ← for agents ◐ medium · /effort
The text after Try is a rotating suggestion, so yours will differ. Read the last line: you are in auto mode before you have typed a word, so Claude Code will run most things without asking. Before the first prompt, press Shift+Tab once. The last line changes to ⏸ manual mode on, and from there every file edit and every command asks first, which is what the next example shows. (If your first screen already says manual mode, the permission-modes section below lists the cases that start a session that way.)
/help opens a dialog with three tabs (General, Commands, Custom commands) and a list of shortcuts. The ones a beginner uses first are these, as printed:
! for shell mode double tap esc to clear input ctrl + shift + _ to undo
/ for commands shift + tab to auto-accept edits ctrl + z to suspend
@ for file paths ctrl + o for verbose output ctrl + v to paste images
/status opens a tabbed panel (Settings, Status, Config, Usage, Stats). The Status tab on 2.1.316:
Version: 2.1.316
Session name: /rename to add a name
Session kind: interactive
cwd: /tmp/lab/r4/order-notifier
Model: Default (Sonnet 5.5 · Efficient for routine tasks)
Setting sources: User settings
Auto mode server: Enabled
System diagnostics
⚠ Native installation exists but ~/.local/bin is not in your PATH. Run:
The lab left out the session id, the socket address and the token line. The Auto mode server line comes from a flag on Anthropic’s side, and it read Disabled in another run of the same version, in the same first session, while the status line under the prompt still said auto mode on. Do not read it as a fault. The warning at the bottom is real and is the same PATH problem as before, which the lab had not fixed in that shell: /status is a second place that tells you. The same panel listed a leftover npm copy at /usr/local/bin/claude, which is the sandbox’s own older install and not something you will have. Note the model line. On a subscription token the default model was Sonnet 5.5, and the welcome line suggested Opus 5.5 with “Switch anytime with /model”.
Now a first real prompt. Say you want a scratch note in the repo, a harmless task a beginner would try. With the session in Manual, the lab typed:
Create a file named NOTES.md containing exactly one line: Checked by Claude Code
and Claude Code stopped and asked:
● Write(NOTES.md)
Create file
NOTES.md
1 Checked by Claude Code
Do you want to create NOTES.md?
❯ 1. Yes
2. Yes, and switch to accept edits (auto-approve file edits and common file commands) for this session
(shift+tab)
3. No
Esc to cancel · Tab to amend
Enter on “Yes” and the file was written:
● Write(NOTES.md)
⎿ Wrote 1 line to NOTES.md
1 Checked by Claude Code
● I created NOTES.md in the repo root. It contains the single line "Checked by Claude Code". I haven't
committed it.
✻ Baked for 2s · done 1:53 AM
That prompt, the approval and the answer cost $0.0402 on /cost. The next section shows where that number comes from. Delete the file with rm NOTES.md and you are back to a clean tree. The screens above are the lab’s, and the words the model writes are not fixed: in a repeat run of this prompt the reply said “in the project root” and not “in the repo root”, the verb in the last line was different, and /cost read $0.0410 and not $0.0402. Expect your wording, your verb and your cost to differ from run to run. The same is true of every sentence in this document that quotes what Claude said, including the dontAsk reply in the permission-mode section below.
Which permission mode should you start in?
There are six modes in 2.1.316, and most tutorials still teach four. The permission modes page lists them, with the config value first and the label the interface shows in brackets where it differs:
default(Manual): reads only, and everything else asks.acceptEdits: reads, file edits in the working directory, and common filesystem commands such asmkdir,touch,mvandcp.plan: reads, and a plan instead of changes.auto: everything, “with background safety checks”, meaning, in the interface’s words, that Claude checks each tool call for risky actions and prompt injection before it runs.dontAsk: only pre-approved tools and reads inside the directory. Anything that would prompt is denied.bypassPermissions: everything, no checks.
The docs also say that from version 2.1.283 an interactive terminal session starts in auto mode on every plan and provider, and the lab’s first screen on 2.1.316 agreed: the status line said auto mode on. If a tutorial tells you that Claude Code starts by asking before every edit, it was written before that. Shift+Tab cycles the modes. On 2.1.316 the lab pressed it four times from the start and saw this order, read off the status line each time: auto, manual, accept edits, plan, and back to auto. dontAsk is not in the cycle, and the docs say bypassPermissions only joins it if you started with that mode enabled. To start in a mode, use the flag, for example claude --permission-mode plan, or set permissions.defaultMode in ~/.claude/settings.json. The CLI also accepts manual as the spelling of the mode whose config value is default, so claude --permission-mode manual and claude --permission-mode default are the same thing.
Auto is the starting mode only when nothing says otherwise. The docs list the cases that start a session in Manual instead: claude -p and Agent SDK runs, disableAutoMode set to "disable" in any settings file, a model that does not support auto mode, Anthropic having turned auto off on its side for your session, and an organization with the HIPAA configuration applied. The lab ran none of these except claude -p with --permission-mode default spelled out (below), so the rest is the docs’ word and not tested, because they need settings, models or an organization the lab did not have.
One trap, and the docs name it in three places. If you write "defaultMode": "auto" or "defaultMode": "bypassPermissions" into a project’s .claude/settings.json or .claude/settings.local.json, it does nothing, with no error. The session falls back to the built-in default, which for bypassPermissions means Manual. Project files are the first place anyone puts a project-wide default, and document 3 teaches that file, so this is worth knowing before you get there. Put those two values in ~/.claude/settings.json, or pass --permission-mode for one session. The lab did not test this one either. It is the permission modes page and the settings page speaking.
The lab ran the same prompt in each mode, headless and in a fresh clone each time at 3e19f24: create NOTES.md with one line, run npm test, and report how many tests passed. -p means print mode: one prompt, one answer, no interactive screen. Each run was capped at $0.50 and used the same subscription token. This is the command, with the mode swapped in each time:
claude -p "Create a file named NOTES.md containing exactly one line: Checked by Claude Code. Then run npm test and tell me how many tests passed." \
--output-format json --permission-mode default --max-budget-usd 0.50 < /dev/null > out-default.json
The dontAsk run added --allowedTools 'Bash(npm test)'. The < /dev/null stops claude -p from waiting three seconds for input on a pipe. --output-format json prints a single line of JSON, and jq pulls the fields out of it. For the default run:
jq -c '{is_error, num_turns, total_cost_usd, model: (.modelUsage|keys), denied: [.permission_denials[]|.tool_name]}' out-default.json
{"is_error":false,"num_turns":3,"total_cost_usd":0.030862900000000002,"model":["claude-sonnet-5-5"],"denied":["Write","Bash"]}
| Mode | NOTES.md written | npm test ran | Cost |
|---|---|---|---|
| default | no | no (denied) | $0.0309 |
| acceptEdits | yes | no (denied) | $0.0339 |
| plan | no | no | $0.0442 |
| auto | yes | yes, 25 passed | $0.0308 |
dontAsk, with --allowedTools 'Bash(npm test)' |
no (denied) | yes, 25 passed | $0.0322 |
| bypassPermissions | yes | yes, 25 passed | $0.0309 |
The cost column moved by up to 10 percent between the lab’s two runs (plan mode read $0.0484 in a repeat, against $0.0442 above), so read it as a size and not a price. The same caution as in the first-session section applies: expect your cost to differ.
Three of those are worth reading closely. In acceptEdits, the file edit went through and npm test did not, because running a test command is not a file edit. Claude Code tried it twice, once with a pipe to tail and once bare, and stopped when both were denied. In dontAsk, the opposite: the one command the lab allowed ran, and the write was refused, and Claude Code said, in one run, “The Write tool was denied because the session is in don’t-ask mode, so I didn’t try to get around it with a shell command.” Its wording changed in a repeat run, and the outcome did not. And in plan, nothing in the repo changed, but it did write a plan file, to a folder under the home directory (.claude/plans/ plus a generated name), and said that ExitPlanMode was not available in the session, so it could not submit the plan for approval. Plan mode in a headless run is a dead end. It is for the interactive session, where document 2 uses it properly.
With no mode on -p, the docs say a run starts in Manual from version 2.1.302 and denies whatever needs approval, unless you add allow rules or --allowedTools. The lab ran the bare form, with no --permission-mode flag, in a fresh clone: the write was denied, npm test was denied twice, no NOTES.md appeared, the exit code was 0 and the cost was $0.0358. Its jq line showed "denied":["Write","Bash","Bash"]. That is the same outcome as --permission-mode default in the table.
Starting in bypassPermissions interactively needs the --dangerously-skip-permissions flag, and the first thing it does is stop you:
WARNING: Claude Code running in Bypass Permissions mode
In Bypass Permissions mode, Claude Code will not ask for your approval before running potentially
dangerous commands.
This mode should only be used in a sandboxed container/VM that has restricted internet access and can
easily be restored if damaged.
By proceeding, you accept all responsibility for actions taken while running in Bypass Permissions mode.
https://code.claude.com/docs/en/security
❯ No, exit
Yes, I accept
Enter to confirm · Esc to cancel
After “Yes, I accept” the status line read bypass permissions on (shift+tab to cycle). The docs add that it refuses to run as root, that some prompts survive it, and that the dialog writes skipDangerousModePermissionPrompt: true to ~/.claude/settings.json. A repeat run on a clean machine saw that last one happen. The lab did not test the other two.
My recommendation: press Shift+Tab once at the start of your first session, so you are in Manual and see every prompt as it was shown above. That is the cheapest way to learn what Claude Code actually does to your files. Move to acceptEdits when you have stopped reading the edit prompts, and to auto when you have decided to trust its safety checks, knowing that the docs themselves say auto “does not guarantee safety”. The lab never saw auto block anything in a task this small, so there is no evidence here about how good the checks are. Keep bypassPermissions for a container that holds nothing you would mind losing. Document 8 is where that argument gets its full treatment.
What does a first session cost?
The first answer is small. The lab sent one word through claude -p on 2.1.316 and asked for the JSON form so the cost would be printed:
claude -p 'reply with the single word: ok' --output-format json < /dev/null
The JSON has a total_cost_usd field, and the usage field holds the token counts. The same call made twice in a row, each pair in a fresh home folder, gave this, with the cost to four decimal places:
| Sign-in | Model | Call 1 | Call 2 | Written to the cache | Read from the cache |
|---|---|---|---|---|---|
Subscription token, with or without --model sonnet |
Sonnet 5.5 | $0.0230 | $0.0230 | 5,442 tokens (1-hour tier) | 11,383 |
API key, --model sonnet |
Sonnet 5.5 | $0.0137 | $0.0137 | 5,002 tokens (5-minute tier) | 11,508 |
| API key, no flag | Opus 5.5 | $0.0829 | $0.0276 | 16,557, then 5,049 | 0, then 11,507 |
One caution on the token row. The second call is not always the same price as the first. In 6 of the lab’s 8 fresh-home pairs the second call, started 2 to 7 seconds after the first one started, wrote the cache again and cost $0.0229. In 2 of 8 it read the first call’s cache write and cost $0.0017. A third call about 20 seconds after the second read 16,818 to 16,821 tokens and cost $0.0017 every time, 6 of 6. So a cold start is about $0.0229 and a warm one about $0.0017, and the lab did not find what decides the case in between. The numbers in this document will not match yours to the fourth decimal. The sentence that begins “Expect your wording, your verb and your cost to differ” in the first-session section applies to every table here.
Three things the lab read off that table, and one it did not. First, the sign-in picks the default model. With the token and no flag, the default was Sonnet 5.5, the same as with --model sonnet. With the API key and no flag it was Opus 5.5. The lab did not find out why the default follows the sign-in, so check /status before you leave a script running. Second, the expensive call was the cold one. Opus’s first call wrote 16,557 tokens to the prompt cache and read none, and cost $0.0829. Its second call read 11,507 tokens back and cost $0.0276. Read the first Opus call against the token’s Sonnet call and it looks like 3.6 times as much ($0.0829 against $0.0230), and it is tempting to put that on the model. Most of that gap is the cold cache. A warm Opus call is about twice a warm Sonnet call ($0.0276 against $0.0137), not 3.6 times.
Third, on the same Sonnet model the key’s call ($0.0137) is cheaper than the token’s ($0.0230). The visible difference is the cache: the token’s calls wrote about 440 more tokens, to the 1-hour tier and not the 5-minute one. Anthropic’s price list puts a 1-hour cache write at twice the base input price and a 5-minute write at 1.25 times (prompt caching docs). Those runs do not prove that is the whole reason. What the lab did not do is check how a subscription is charged: the figures are the tool’s own, labelled list in the JSON, which the lab takes to mean list price, and on a plan that is not necessarily what you are billed.
The token run also reported 2 fresh input tokens, 5,442 written to the cache, 11,383 read, and 4 output tokens. Add the three input figures and Claude Code sent about 16,800 tokens of its own starting context before the one word. You pay for that on every session.
The interactive session had its own number. After the one-file prompt above, /cost printed:
Total cost: $0.0402
Total duration (API): 4s
Total duration (wall): 50s
Total code changes: 2 lines added, 0 lines removed
Usage by model:
claude-haiku-5-5: 1.2k input, 18 output, 0 cache read, 0 cache write ($0.0001)
claude-sonnet-5-5: 510 input, 148 output, 91.8k cache read, 7.1k cache write ($0.0400)
Prompt cache (main): 2 requests · 89% of input tokens from cache · no misses · warm (1h TTL, last
activity 12s ago)
Note that a second, smaller model (Haiku 5.5) shows up for $0.0001. The lab does not know what it did, and did not pick it. The lab did not check how the plan usage on the /usage tab moves. For scale, the costs page in the docs says, for enterprise deployments, that the average cost is around $13 per developer per active day and $150-250 per developer per month, with costs below $30 per active day for 90% of users. That is Anthropic’s number, over many teams, and it says nothing about your first hour. Your first hour will not match the lab’s. The lab’s one-file task came to about four cents.
Whatever your number is, the shape of the problem is real. Mettons a script runs the one-word call from above ten times a second for an eight-hour night. That is 10 × 3,600 × 8 = 288,000 calls, and at $0.0229503 a call it is $6,609.69. I made the loop up, and the cache would make later calls cheaper, so treat it as a ceiling for that prompt and not a forecast. It is the shape that matters, and a cap in front of the spend is how you find the number before the invoice does. I wrote about that for a different tool in the LiteLLM budget post.
How do you uninstall it?
The docs give two commands for the native install, and they leave some folders behind:
rm -f ~/.local/bin/claude
rm -rf ~/.local/share/claude
After those two, the run found ~/.local/bin, ~/.local/state/claude/locks and ~/.cache/claude/staging still on disk, all empty, plus the ~/.claude folder and ~/.claude.json. The docs add rm -rf ~/.claude and rm ~/.claude.json for the settings. None of these commands touches the export PATH=... line you added to ~/.bashrc, so take that out by hand. Do that and the only things left are the empty folders, which are harmless. This run was against 2.1.316.
Who should not install Claude Code yet?
Anyone who cannot send source code to an outside service should not install Claude Code on that code, and that holds for every way to install Claude Code. Claude Code sends the contents of the files it reads to the model provider you signed in with, which is Anthropic’s API for an account or key, and the cloud provider you chose if you use Bedrock, Vertex or Foundry. No install method changes that. If your company’s rules forbid it, stop here and read Anthropic’s data usage page first. It covers the training and retention terms for each plan. It is a policy page and not a command, so nothing on it was run.
Also skip it if your machine is below the docs’ floor, a 2 GB virtual machine, say, since the docs ask for 4 GB, and the lab did not test lower. And skip the native installer if your policy bans curl | bash: use npm or a package manager and pin the version yourself.
What is next
Document 2 is the session loop: @ file references, /clear and /compact, CLAUDE.md, plan mode as a workflow, and resuming a session. Before then, one number to watch on your own machine: run claude --version today and again in a week, without touching anything, and see whether the build under your fingers is the one this guide ran.
Next: Claude Code Session: Context, Memory and Plan Mode | Back to the series