Sandboxing an Agent by Default
Running an agent under wardcell is explicit — you type
wardcell run -- claude, not
claude. That is the right default for wardcell itself: nothing is sandboxed
unless you say so. But for an agent you always want sandboxed, the wardcell run -- in front of every invocation is friction, and friction is what gets
skipped at 6pm on a Friday.
A shell alias removes it. Alias claude to the sandboxed form, and typing the
bare name does the safe thing — with the real binary one keystroke away for the
times you mean to bypass it.
The alias
Section titled “The alias”Put this in your shell’s startup file — ~/.zshrc, ~/.bashrc, or the fish
file below — and open a new shell:
alias claude='wardcell run -- claude'alias codex='wardcell run -- codex'alias --save claude='wardcell run -- claude'alias --save codex='wardcell run -- codex'Now claude expands to wardcell run -- claude, claude --resume to wardcell run -- claude --resume, and so on — every argument rides along.
It does not loop. The claude after -- is an argument to wardcell, not
a command the shell re-reads, so the alias fires once and no further. wardcell
then execs the real claude off your PATH directly — it never goes back
through an interactive shell, and the sandboxed process gets an
empty-plus-policy environment that never reads
your .zshrc at all. There is nowhere for the alias to re-trigger.
Bypassing it once
Section titled “Bypassing it once”To run the real binary unsandboxed for a single command — without removing the alias — sidestep alias expansion:
command claude --version # or: \claude --versioncommand claude --versionAdding your own defaults
Section titled “Adding your own defaults”If every sandboxed run of an agent should carry the same flag — a non-default
policy root, a --profile, a
--zone — a function reads more clearly than a long alias and still takes your
extra arguments:
claude() { wardcell run --profile developer -- claude "$@"}function claude wardcell run --profile developer -- claude $argvendA function does not loop either, for the same reason the alias does not: its
command word is wardcell, and the claude it names is an argument wardcell
resolves off PATH.
macOS: the CLI, not the Dock
Section titled “macOS: the CLI, not the Dock”An alias is a shell thing, so it covers the agent you launch by typing its name.
It does not reach a GUI launch — double-clicking Claude.app, or open -a,
hands the launch to launchd, and
a process launchd starts never enters the sandbox.
To sandbox the desktop application, launch its bundle through wardcell instead:
wardcell run -- /Applications/Claude.appwhich you can alias the same way if you launch it from a terminal.
Making it unavoidable
Section titled “Making it unavoidable”The alias is deliberately only as strong as your habit of typing the name. The
step up in coverage is a PATH shim — a small wrapper script named claude
placed in a directory earlier on $PATH than the real one, so that scripts and
other programs resolving claude by name hit the wrapper too. It is a sharp
tool, and worth being clear about its two edges before reaching for it:
- It can loop. wardcell resolves the command off
PATHjust as the shell does, so a wrapper that callswardcell run -- claudesends wardcell straight back to the wrapper. The wrapper has to hand wardcell the real binary by absolute path —wardcell run -- /usr/local/bin/claude.real "$@"— which means renaming the real binary and keeping the two in step across upgrades. - It is still not a boundary. Anything that calls the agent by its absolute
path, or sets its own
PATH, walks straight past the shim. APATHshim raises the floor; it does not seal the room.
The only way to make an unsandboxed launch genuinely impossible is to remove or rename the real binary so the sandboxed path is the only path — at which point the wrapper is not raising a floor, it is the binary. That is a real option for a locked-down machine, but it is a deployment decision, not a shell alias, and wardcell does not manage it for you.
wardcell’s own boundary is the sandbox the run executes inside, not the launcher
that starts it. On a managed fleet, pin that boundary where it belongs: a
root-owned policy root
no run can weaken, and
policy_root.allow_override = false so no
run can point itself at a softer one. The alias is for the single developer who
wants the safe thing to be the easy thing — not the control that makes it the
only thing.