Run an AI coding agent as a dedicated Linux user

Isolating an AI coding agent means running it where it cannot read your files or credentials. The options are a dedicated user (the cheapest), a container, and a virtual machine: pick the user for your own machine and a VM for code you do not trust. Woof's thepack runs Claude Code, Codex and other coding agents this way from a shared board, and the steps below set up the user, with a systemd user service.

Dedicated user, container or VM

What each option protects, and what it does not. The last row is what Woof adds on top of any of them.

OptionProtectsDoes not protectEffort
Dedicated Linux userYour files (home 0700), your ssh, gh and cloud credentials, your browser sessions. A bad run reaches only that user's home and what its forge token can reach.The network, the kernel, anything world-readable, a container socket the user can reach.Minutes, no extra tooling. Steps 1 to 8 below.
Container (Docker, Podman)A filesystem of its own: the agent sees only what you mount. Easy to throw away and rebuild.The kernel is shared, so a kernel or runtime escape is still possible. A mounted socket or a privileged flag undoes the wall.An image with the toolchain, mounts, a way to give it credentials. Not covered by this guide.
Virtual machineIts own kernel: the strongest wall of the three, and the one to pick for code you do not trust.Whatever you share into it (folders, tokens, network). Heavier: disk, memory, updates.The most setup and upkeep. Not covered by this guide.
What Woof addsA board to run the isolated agent from: assign a card, follow the work, answer its questions, review the pull request. A lease and a fencing token per card so two agents never take the same one, and several agents and machines in one pack. Woof does not replace the wall: thepack runs the agent with the rights of the user who starts it, so the isolation is what you set up here.

The rest of this guide sets up the first option. The container and VM rows are general descriptions, not steps we have replayed with Woof.

1. Create the user

sudo useradd -m -s /bin/bash agent
sudo passwd -l agent
sudo chmod 700 /home/agent

Do not add it to the sudo or wheel group. The chmod 700 matters: Debian creates homes as 0755, which lets each user read the other's files.

2. Let its services run while logged out

sudo loginctl enable-linger agent
sudo -iu agent

Without linger, systemd stops the user's services when its last session ends. The options are -iu, in that order. If systemctl --user complains about the bus, open the shell with:

sudo machinectl shell agent@.host

3. Give it a toolchain

As agent, install Node.js 22+ (it brings npm), git and the provider CLIs in its own home, not yours. With n:

mkdir -p "$HOME/.local/bin"
export N_PREFIX="$HOME/.n"
export PATH="$N_PREFIX/bin:$HOME/.local/bin:$PATH"
curl -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n -o "$HOME/.local/bin/n"
chmod 755 "$HOME/.local/bin/n"
n lts
node --version

Make the PATH permanent with absolute entries, in a file a sudo -iu login shell reads, so a later thepack service always sees it:

cat >> "$HOME/.bash_profile" <<'PROFILE'
export N_PREFIX="$HOME/.n"
export PATH="$N_PREFIX/bin:$HOME/.local/bin:$PATH"
PROFILE

The provider CLIs (claude, codex, opencode, cursor-agent) come later: thepack tells you which one is missing, with the install command. A binary that exists only in your home, such as fgh in ~/.go/bin, is invisible to the user. As you, copy it:

sudo install -m 755 -o agent -g agent ~/.go/bin/fgh /home/agent/.local/bin/

4. Keep every PATH entry absolute

This is the pitfall. thepack service writes the PATH of the shell that runs it into the systemd unit and never reads .bashrc. A relative entry such as PATH=$PATH:.n/bin makes Go's exec.LookPath fail with ErrDot, and thepack reports npm not found although npm runs in your shell. A stale shell keeps its old PATH and bash adds to it at every nesting, so leave it and start from a clean login shell:

exit
sudo -iu agent
export N_PREFIX="$HOME/.n"
export PATH="$N_PREFIX/bin:$HOME/.local/bin:$PATH"

After step 5, check what the unit really got:

systemctl --user show thepack -p Environment | tr ' ' '\n' | grep PATH

5. Install and connect thepack

As agent. thepack login opens a woof page and the approval comes back to a loopback address. On a machine without a browser, or when the browser is on another host, add --remote: approve in a browser elsewhere, then paste the address it ends on.

curl --proto '=https' --tlsv1.2 -fsSL https://get.woof-board.sh/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
thepack login
thepack service

Headless variant: replace the login with thepack login --remote.

Follow its logs:

journalctl --user -u thepack.service -f

6. Give it its own forge credentials

Never reuse yours. Either register an SSH key of its own as a deploy or machine-user key, or use a dedicated token you can revoke, with the minimum scope: repository read and write. Read the token on stdin, never as an argument, because the shell history keeps arguments:

read -rs T && echo "$T" | fgh auth login --hostname git.example.com --with-token; unset T
read -rs T && echo "$T" | gh auth login --with-token; unset T

thepack tries the gh or fgh token, then ssh, then https, and lists each failure on the card when all three fail.

7. Log in to the providers

As agent, run the provider's own login, so subscriptions and sessions are the user's, not yours:

claude
codex login

If an install changed the PATH, open a fresh sudo -iu agent shell (it reads ~/.bash_profile) and run thepack service again.

8. Check it works

  1. On the pack page, the machine shows online.
  2. Choose Add agent, and Check again on any provider problem.
  3. Give a card to the agent: it clones the repository and runs.

New to Woof? Start with the MCP and woof-agentd documentation.

What it protects, and what it does not

Protects: your files (home 0700), your gh, ssh and cloud credentials, your browser sessions. The Woof machine token stays 0600 in the agent user's home. A bad run can reach that user's home and what the forge token can reach, nothing more.

Does not protect: the network, the kernel, anything world-readable, a container socket the user can reach (a rootless podman socket behind DOCKER_HOST is still its own power), and every repository the forge token can write. This is not a sandbox. For a stronger wall, use a container or a VM.

Run these isolated agents from a board

Once the agent is isolated, the problem is running it: opening a terminal per agent, watching each one, and finding out later what it did. Woof puts that on a board. You write a card and assign it to Claude Code, Codex or another agent on your machine. You follow its work on the card, answer when it asks a question, and review the pull request it opens. Several agents and several machines share the same board, and a lease keeps two of them off the same card.

Read how Woof coordinates people and agents, or how it compares with task boards you may already use. To connect your own client, see connecting an AI agent over MCP.

Questions

How do I sandbox Claude Code?
Run it as a user that cannot read your files or credentials: create a dedicated Linux user without sudo, make its home private (0700), and start the agent from that user's own systemd user service. For a stronger wall, run it in a container or a VM. Woof's thepack does the first for you from a board: connect the machine once, add Claude Code as an agent, and hand it cards. This guide gives the steps.
Is a separate Linux user enough to isolate a coding agent?
It is enough to keep the agent away from your own files, ssh keys, gh login and browser sessions, and that is the common risk. It is not a sandbox: the network, the kernel and anything world-readable stay reachable, and so does every repository the agent's forge token can write. Give it a token scoped to the repositories it needs, and use a container or a VM for code you do not trust.
Docker or a VM for a coding agent?
A container is quicker to set up and to throw away, but shares your kernel. A VM has its own kernel and is the safer pick for untrusted code, at the cost of more setup and resources. Whichever you choose, avoid mounting your home, the Docker socket or your credentials into it. Running Woof's thepack inside a container or a VM is not documented or tested here yet.
How do I run several coding agents at once?
Give each agent its own identity and let a board hand out the work, so two agents do not take the same task. In Woof, assign cards to several agents, each holds a card under a lease and a fencing token, and the board shows who is working on what. Several machines can join the same pack. Review stays with you: each result comes back as a pull request.
What does Woof add once the agent is isolated?
A board to run it from. Without one, you start each agent in a terminal and watch it. With Woof you assign a card, follow the agent's work, answer its questions when it is stuck, and review the pull request, for one agent or several, on your own machine.

Troubleshooting

npm not found on thepack's PATH
Almost always a relative PATH entry (PATH=$PATH:.n/bin): Go refuses it (exec.LookPath returns ErrDot) although npm runs in your shell. Start a clean login shell, export absolute entries, run thepack service again and check the unit's PATH (step 4).
Permission denied (publickey)
The user has no SSH key the forge accepts. Register its own key as a deploy or machine-user key, or use a token (step 6). Never copy your own key.
fgh has no login for <host>
Run fgh auth login --hostname <host> --with-token as the agent user, not as you.
could not read Username for 'https://...': terminal prompts disabled
https was tried last and has no credentials. thepack tries the gh/fgh token, then ssh, then https, and lists each failure on the card: fix the first one (step 6).
fgh: command not found
The binary lives only in your home. Copy it with sudo install -m 755 -o agent -g agent /path/to/fgh /home/agent/.local/bin/ and add that directory to the PATH (step 3).
sudo: unknown user i
The options were swapped: it is sudo -iu agent (-iu, in that order), not sudo -ui agent.