Hooks and sandboxing
Hooks intercept every call before and after it runs; a sandbox bounds what a shell command can reach, whatever the rules allowed.
Loading the animation…
Concept
Permission rules (chapter 5) are one checkpoint. A harness has two more. Hooks are the user's own code, run by the harness at fixed points: a pre-tool hook sees every call before it runs and can block it or rewrite it; a post-tool hook sees the result and can add to it. A sandbox is a boundary the operating system enforces around the commands the agent runs: whatever the rules allowed, a sandboxed command cannot write outside the workspace or reach the network.
The task is the failing test again, after which the scripted model wants to install a coverage plugin (pip install pytest-cov), clear the build directory (rm -rf build) and clear a cache in the home directory (rm -rf ~/.cache/pytest). Three hooks are on offer: no-rm blocks any rm; quiet-tests rewrites pytest to pytest -q; lint appends a lint result after every edit. Follow one call through the checkpoints in the animation, then change the sandbox:
- No sandbox, no hooks: every call runs. On a real machine,
rm -rf ~/.cache/pytestwould have deleted part of your home directory. - Workspace sandbox: writes inside the repository and no network. The install and the home-directory delete are stopped (2 calls), while
rm -rf build, inside the repository, still runs. - Read-only sandbox: no writes at all; 3 shell calls are stopped,
rm -rf buildamong them. The edit that fixes the test still happens: it is a file tool, not a shell command, and the sandbox wraps the shell only. That is how the public sandboxes work too (see the table below), and why permission rules still matter inside one. - With the hooks:
no-rmblocks both deletes before the sandbox is reached (2 calls blocked), so in workspace mode the sandbox stops only the install. The rewrite and the lint note change what the model sees, at no risk.
Order matters. A hook runs before the tool; the sandbox applies while it runs. A hook can stop a call the sandbox would have allowed (deleting the build directory), and the sandbox stops what no rule or hook thought of (a path outside the workspace, written some other way). Neither replaces the other.
Maths
Write a call's fate as three predicates, checked in order: the permission decision (with a human's answer if the rules asked), the pre-tool hooks' , and for a shell command the sandbox's :
The first two are the harness's decisions, made before anything runs, from what the call says. The third is the operating system's, made while it runs, from what the call does. A rule or a hook that misreads a command (a shell alias, a script that deletes files) lets it through; the sandbox does not read commands at all. Hooks cost time too: here the three hooks change the run's time from 14.9 s to 13.4 s, because the two blocked deletes never run.
Code
A pre-tool hook, cut from the vendored engine (harness.ts): a block ends the call before it reaches the tool.
if (h.action === "block") {
this.emit("hook", agent, this.t, { call: cid, hook: h.name, phase: "pre", action: "block", result: "blocked" });
outcome = { cid, call: c, args, outcome: "blocked", text: `Blocked by hook ${h.name}: ${h.message ?? "not allowed"}` };
break;
}
And the sandbox's check on a shell command (tools.ts):
if ((NETWORK_COMMANDS.includes(cmd) || installs) && !(sandbox.network ?? false))
return `${cmd}: network access is blocked by the sandbox`;
The sandbox in the public harnesses
| Harness | Hooks and sandbox (from their docs, accessed 2026-10-07) |
|---|---|
| Claude Code | PreToolUse hooks can block a call or replace its arguments (updatedInput); PostToolUse can replace the result (hooks). The Bash sandbox "is off by default"; when on, commands write to the working directory and reach the network through allowed domains; "The sandbox covers shell commands only" (sandboxing) |
| Codex CLI | PreToolUse can deny a call or return updatedInput; PostToolUse can add context (hooks). Sandbox modes read-only, workspace-write and danger-full-access, with the network off by default in workspace-write (approvals and security) |
| OpenHands | PreToolUse hooks can stop a tool call (hooks); a Docker sandbox, the recommended default, runs the agent's commands in a container (sandboxes) |