Pass hooks to run(). Here a sandbox hook, a shell command Sandcastle runs inside the sandbox while it starts, installs dependencies before the agent starts:
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
await run({
agent: claudeCode("claude-opus-4-8"),
sandbox: docker(),
promptFile: ".sandcastle/prompt.md",
hooks: {
host: {
onSandboxReady: [{ command: "echo sandbox is up" }],
},
sandbox: {
onSandboxReady: [{ command: "npm install", timeoutMs: 300_000 }],
},
},
});Hooks are grouped by where they run — host (on the developer's machine) or sandbox (inside the sandbox):
hooks: {
host: {
onWorktreeReady: [{ command: "cp .env.example .env" }],
onSandboxReady: [{ command: "echo sandbox is up" }],
},
sandbox: {
onSandboxReady: [
{ command: "npm install", timeoutMs: 300_000 },
{ command: "apt-get install -y ffmpeg", sudo: true },
],
},
}| Hook | Runs on | When | Working directory |
|---|---|---|---|
host.onWorktreeReady |
Host | After copyToWorktree, before sandbox start |
Worktree path (host repo root under head) |
host.onSandboxReady |
Host | After sandbox is up | Worktree path (host repo root under head) |
sandbox.onSandboxReady |
Sandbox | After sandbox is up | Sandbox repo directory |
Ordering: copyToWorktree -> host.onWorktreeReady (sequential) -> sandbox created -> host.onSandboxReady + sandbox.onSandboxReady (parallel).
- Host hooks run their command on your machine instead of in the sandbox. They accept
{ command: string; timeoutMs?: number }— nosudo, nocwd. Usecdor inline env in the command string. - Sandbox hooks accept
{ command: string; sudo?: boolean; timeoutMs?: number }— setsudo: trueto run the whole command as root. Ondockerandpodmanthis runs it as the container's root user, so the image needs nosudobinary; ondaytona,spritesandvercelit uses the sandbox's ownsudo; onmodalevery command already runs as root.sudo: trueisn't available on the native providers (bwrap,seatbelt), which have no root: the hook fails, naming the provider and pointing todocker(). Install system packages on your host instead. timeoutMsoverrides the default 60 s per-hook timeout. Useful for long-running setup commands like dependency installs (e.g.timeoutMs: 300_000for 5 minutes).- Within each hook point, sandbox hooks run in parallel; host hooks within
onSandboxReadyalso run in parallel with sandbox hooks.host.onWorktreeReadyhooks run sequentially in declared order. - If any hook exits non-zero, setup fails fast.
- When a
signalis passed torun(), it is threaded to all hooks — aborting the signal cancels any in-flight hook commands.
- Core concepts: the host and the sandbox
- Branch strategies: the worktree that host hooks run in
- Sandbox providers: which providers support
sudo: true - Prompts: shell expressions, which run after sandbox hooks
run()reference: thehooksoption