
Claude Code is Anthropic's agentic coding tool. You run it in a terminal, describe what you want in ordinary prose, and it reads your files, runs commands, edits code, and reports back. It is worth being precise about what that means at the operating system level, because the whole of this chapter follows from it: Claude Code is a program that spawns child processes on your behalf. It runs as your user, with your permissions, in your working directory. Everything Chapter 9 said about users and permissions and everything Chapter 10 said about processes applies to it without modification.
- Install Claude Code on a Linux system using the native installer or a signed package repository
- Explain the agent loop and why an assistant that runs shell commands is a normal Unix process
- Give a project persistent instructions with CLAUDE.md and configure permissions in settings.json
- Enable and reason about the bubblewrap sandbox, including filesystem and network isolation
- Use Claude Code non-interactively in pipelines and scripts with the print flag
That is the source of both its usefulness and its risk, and it is the reason the video above starts by putting the tool inside a virtual machine rather than on the host.
Start in a Machine You Can Throw Away
An assistant that can run rm is an assistant that can run rm on the wrong thing. The mitigations exist and this chapter covers them, but the most robust one is the oldest idea in this book's later chapters: put the work somewhere that does not matter.
A virtual machine or container gives you a blast radius you control. If a session goes wrong, you discard the machine and start again, and your actual laptop is untouched. It also gives you a clean, reproducible Linux environment, which matters if you are learning: the commands in this chapter behave identically on a fresh Ubuntu VM and unpredictably on a personal machine with fifteen years of accumulated configuration.
Chapter 17 covered the options. In rough order of isolation strength and setup cost:
Table 22.1: Ways to give an agent a disposable environment
| Approach | Isolation | Notes |
|---|---|---|
| Container (Docker, Podman) | Namespaces and cgroups | Fastest to start, shares the host kernel |
| Dev container | Namespaces and cgroups | Config committed to the repository, reproducible per project |
| Virtual machine (KVM, VirtualBox, UTM) | Full hardware virtualisation | Strongest boundary, slowest to start |
| Cloud VM | Full, and not your hardware | Nothing local to damage at all |
Anthropic publishes a reference dev container configuration, which runs Claude Code as a non-root user. That last detail matters more than it looks: running an agent as root removes the permission checks that would otherwise catch a mistake, and Claude Code deliberately refuses to run with permission prompts disabled when it detects it is running as root or under sudo on Linux.
Installing on Linux
Claude Code supports Ubuntu 20.04 and later, Debian 10 and later, and Alpine Linux 3.19 and later, on x64 or ARM64, with at least 4 GB of RAM. It needs an internet connection, since the model runs remotely.
The recommended method is the native installer:
curl -fsSL https://claude.ai/install.sh | bash
Piping a downloaded script straight into a shell is a pattern this book has been rude about before, and the reservation stands. If you would rather inspect the script first, download it, read it, then run it. On Debian and Ubuntu there is a better option: a signed apt repository, which gets you the distribution's normal signature verification and integrates with your usual upgrade workflow.
sudo apt install curl gnupg
sudo install -d -m 0755 /etc/apt/keyrings
sudo curl -fsSL https://downloads.claude.ai/keys/claude-code.asc \
-o /etc/apt/keyrings/claude-code.asc
Before trusting the key, check its fingerprint. This is exactly the verification step Chapter 11 described for package signing, and it is worth performing rather than skipping:
gpg --show-keys /etc/apt/keyrings/claude-code.asc
The fingerprint should read 31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE. If it does, register the repository and install:
echo "deb [signed-by=/etc/apt/keyrings/claude-code.asc] https://downloads.claude.ai/claude-code/apt/stable stable main" \
| sudo tee /etc/apt/sources.list.d/claude-code.list
sudo apt update
sudo apt install claude-code
Equivalent signed repositories exist for dnf on Fedora and RHEL, and for apk on Alpine. There is also an npm package, @anthropic-ai/claude-code, which installs the same native binary rather than a JavaScript program; do not install it with sudo.
Verify the result:
claude --version
claude doctor
claude doctor is the diagnostic command. It prints installation health, validates your settings files, and reports the outcome of the most recent update attempt, all without starting a session. Run it first whenever something is behaving oddly.
Authentication requires a paid Claude subscription (Pro, Max, Team, or Enterprise) or an Anthropic Console account; the free Claude.ai tier does not include Claude Code. Running claude for the first time opens a browser login. If ANTHROPIC_API_KEY is set in the environment, it prompts you to approve that key instead.
The Agent Loop
Start a session by changing into a project and running the command with no arguments:
cd ~/projects/myapp
claude
What follows is a conversation, but the mechanics underneath are a loop. You state a goal. The model decides which tool it needs, most often reading a file, searching with ripgrep, or running a shell command. The tool runs, its output returns to the model, and the model decides what to do next. The loop continues until the task is finished or it needs a decision from you.
Two consequences follow from this design, and both are easy to miss.
The first is that the model's picture of your project comes entirely from tool calls. It has not indexed your repository in advance. When it seems not to know about a file, the usual reason is that nothing has caused it to read that file yet.
The second is that every action is a real action. There is no preview mode by default: when the model decides to run npm test, the test suite actually runs, on your machine, as you. This is why the permission system exists.
Persistent Instructions with CLAUDE.md
A session starts with an empty context window. Anything you explained last time is gone. The fix is a file named CLAUDE.md, placed in your project root, which is loaded at the start of every session.
Use it for the facts you would otherwise retype: build and test commands, coding conventions, architectural decisions, the location of things. Claude Code can generate a first draft by analysing the repository:
claude
> /init
Several locations are read, from broadest scope to narrowest, and all of them are concatenated rather than overriding one another:
Table 22.2: Where instruction files live, in load order
| Scope | Location | Shared with |
|---|---|---|
| Managed policy | /etc/claude-code/CLAUDE.md |
Everyone on the machine, deployed by IT |
| User | ~/.claude/CLAUDE.md |
Just you, across all projects |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md |
Your team, via version control |
| Local | ./CLAUDE.local.md |
Just you, this project; add it to .gitignore |
The file is context, not configuration. Claude reads it and tries to follow it, but it is not enforcement: an instruction in CLAUDE.md shapes behaviour, it does not constrain it. If you need something to happen without fail, use a hook or a permission rule instead. Keep the file short, under about 200 lines, and make instructions specific enough to check. "Use 2-space indentation" works; "format code properly" does not.
Claude Code also maintains its own notes, called auto memory, under ~/.claude/projects/<project>/memory/. These are plain markdown files that accumulate build commands and debugging insights across sessions. They are yours to read, edit, or delete; /memory opens them.
Permissions
Permission rules decide which tool calls run and which stop to ask. They live in settings.json files that follow the same broad-to-narrow layering as instruction files: managed policy, then user settings at ~/.claude/settings.json, then project settings at .claude/settings.json, then your personal local overrides in .claude/settings.local.json.
Rules are written as tool names with optional argument patterns:
{
"permissions": {
"allow": [
"Bash(npm run test:*)",
"Read(src/**)"
],
"ask": [
"Bash(git push:*)"
],
"deny": [
"Read(.env)",
"Read(~/.ssh/**)",
"Bash(curl:*)"
]
}
}
Deny rules win. The deny list above is worth copying: reading .env files and SSH keys is exactly the capability you do not want an agent to have, and denying it costs you nothing.
Permission modes control the default posture for a whole session. The default mode prompts before anything that modifies state. Plan mode restricts the agent to analysis until you approve a plan. And then there is --dangerously-skip-permissions, which turns the prompts off entirely. The flag is named that way on purpose. Use it only inside a container or VM you are prepared to lose, which brings us back to where the chapter started.
The Sandbox
Permission rules are checked before a command runs, and they are checked against the command string. That is useful but shallow: a command can do more than its name suggests, and the check happens in Claude Code, not in the kernel.
The sandbox is the deeper layer. When enabled, shell commands run inside an OS-enforced boundary that applies to the command and every child process it spawns, regardless of what the model intended. On Linux it is built from two packages you install yourself:
sudo apt-get install bubblewrap socat
bubblewrap is an unprivileged sandboxing tool built on user namespaces; it enforces the filesystem boundary. socat relays network traffic through a proxy that enforces the domain allowlist. Inside a session, /sandbox opens a panel showing the current mode, any missing dependencies, and the resolved configuration.
The defaults are: write access to the working directory and the session temp directory only, read access to most of the filesystem, and no network domains pre-allowed. The first time a command needs a new domain, you are asked.
{
"sandbox": {
"enabled": true,
"filesystem": {
"allowWrite": ["/tmp/build"]
},
"network": {
"allowedDomains": ["github.com", "*.npmjs.org"]
},
"credentials": {
"files": [
{ "path": "~/.ssh", "mode": "deny" },
{ "path": "~/.aws/credentials", "mode": "deny" }
]
}
}
}
Note the credentials block. The default read policy still allows reading ~/.ssh and ~/.aws/credentials, so denying them is a deliberate act rather than something you get for free.
The Ubuntu 24.04 Wrinkle
There is a Linux-specific complication here that is a good illustration of kernel security policy in practice. Ubuntu 24.04 tightened its AppArmor defaults to restrict unprivileged user namespaces, the exact facility bubblewrap needs. Check whether you are affected:
sysctl kernel.apparmor_restrict_unprivileged_userns
If that returns 1, bubblewrap cannot create the namespaces it needs, and the sandbox will not start. The fix is an AppArmor profile granting bwrap the capability:
sudo tee /etc/apparmor.d/bwrap > /dev/null <<'EOF'
abi <abi/4.0>,
include <tunables/global>
profile bwrap /usr/bin/bwrap flags=(unconfined) {
userns,
include if exists <local/bwrap>
}
EOF
sudo systemctl reload apparmor
The profile applies to bwrap itself, not to the commands running inside the sandbox. If the file returns 0, or does not exist at all, you have nothing to do.
Be clear about what the sandbox is and is not. It is a strong reduction in risk and a weak isolation boundary. The network proxy makes its decision from the hostname the client supplies and does not by default inspect TLS traffic, so a broadly allowed domain can become an exfiltration path. Allowing writes to a directory on $PATH, or to ~/.bashrc, hands back the code execution the sandbox was meant to contain. If you need a hard boundary, use a VM.
Claude Code as a Unix Program
Claude Code is composable, which is what makes it interesting to a Linux user rather than merely to a programmer. The -p flag runs a single prompt and exits, printing to standard output:
claude -p "summarise what changed in the last five commits"
Because it reads standard input, it drops straight into a pipeline:
tail -200 /var/log/syslog | claude -p "any errors worth worrying about?"
git diff main --name-only | claude -p "review these files for security issues"
For scripting, --output-format json gives you a structured result to parse rather than prose. --max-turns bounds how long the loop may run, and --max-budget-usd bounds what it may spend. Sessions can be resumed with -c for the most recent, or -r for one named explicitly.
Everything in Chapter 7 about pipes and redirection applies. The tool was designed to obey the same conventions as grep and sort, and it rewards being treated as another filter in a chain rather than as a separate application you visit.
Extending It
Three extension points are worth knowing about, each of which maps onto something you already understand.
Hooks run shell commands at fixed points in the agent's lifecycle: before a tool call, after a file edit, when a session starts. Because they are shell commands executed by the client rather than requests to the model, they run regardless of what the model decides, which makes them the right tool for anything that must always happen. Auto-formatting after every edit is the canonical example.
Skills package a repeatable workflow into a folder with instructions, loaded on demand rather than held in context permanently.
MCP, the Model Context Protocol, is an open standard for connecting the agent to external systems: issue trackers, databases, documentation, your own internal services. An MCP server is a separate process the agent talks to over a defined protocol, which is a familiar shape if you have ever written a daemon.
Removing It
Uninstalling follows your install method. For the native installer:
rm -f ~/.local/bin/claude
rm -rf ~/.local/share/claude
For the apt package:
sudo apt remove claude-code
sudo rm /etc/apt/sources.list.d/claude-code.list /etc/apt/keyrings/claude-code.asc
Configuration and session history live separately, in ~/.claude and ~/.claude.json, and survive an uninstall until you remove them explicitly.
The next chapter looks at OpenAI's Codex CLI, which solves the same problem with a noticeably different approach to sandboxing.
