How to Configure Lifecycle Hooks in Sandcastle: Complete Guide to onWorktreeReady
Configure lifecycle hooks in Sandcastle by passing a hooks object to run() or createWorktree(), using host.onWorktreeReady to execute commands after the worktree is created but before the sandbox starts.
Sandcastle is a sandboxing framework for running AI agents in isolated environments. When you configure lifecycle hooks, you can execute custom commands at precise moments in the sandbox lifecycle—such as installing dependencies or preparing files before the container launches. The onWorktreeReady hook is particularly useful because it fires immediately after the git worktree is created, allowing you to modify the workspace before Sandcastle starts the Docker, Podman, or bind-mount sandbox.
Understanding the onWorktreeReady Hook
The host.onWorktreeReady hook executes after the worktree is created and after any copyToWorktree files are copied, but before the sandbox container starts. This timing ensures you can perform host-side setup operations while having access to the complete worktree directory.
According to the Sandcastle source code, this hook is triggered in three locations:
src/interactive.ts(lines 279-285) – When running interactive sessionssrc/createWorktree.ts(lines 241-245) – When using the low-level worktree APIsrc/SandboxFactory.ts(lines 552-556) – During sandbox factory orchestration
The hook system is defined by the SandboxHooks type in src/SandboxLifecycle.ts, which also exports the runHostHooks helper function that executes your commands.
How to Configure onWorktreeReady in run()
The most common way to configure lifecycle hooks is through the run() function's options object.
Basic Configuration
Pass a hooks object with host.onWorktreeReady containing an array of command objects:
import { run } from "sandcastle";
import { docker } from "sandcastle/sandboxes";
await run({
agent: claudeCode("claude-opus-4-7"),
sandbox: docker({ imageName: "myrepo/sandcastle:latest" }),
prompt: "Please refactor the utils module.",
hooks: {
host: {
onWorktreeReady: [{ command: "npm install" }],
},
},
});
In this example, Sandcastle executes npm install inside the worktree directory immediately after creation, then proceeds to launch the Docker sandbox only if the command succeeds.
Multiple Commands and Custom Timeouts
You can chain multiple commands with individual timeouts. The default timeout is 60 seconds (HOOK_TIMEOUT_MS = 60000 in src/SandboxLifecycle.ts), but you can override this per command:
await run({
agent: claudeCode("claude-3-5-sonnet"),
sandbox: docker({ imageName: "node:18" }),
prompt: "Analyze the codebase.",
hooks: {
host: {
onWorktreeReady: [
{ command: "npm ci", timeoutMs: 30_000 },
{ command: "npm run generate-types", timeoutMs: 45_000 },
{ command: "git status" },
],
},
},
});
Commands run sequentially using execAsync. If any command fails or times out, Sandcastle throws an ExecError or HookTimeoutError and aborts the entire run() operation.
Combining with copyToWorktree
The onWorktreeReady hook works seamlessly with the copyToWorktree option, which copies files from the host into the worktree before the hook runs:
await run({
agent: claudeCode("claude-3-opus"),
sandbox: docker({ imageName: "python:3.11" }),
prompt: "Run the test suite.",
copyToWorktree: ["node_modules", ".env.local"],
hooks: {
host: {
onWorktreeReady: [
{ command: "npm rebuild" },
{ command: "chmod +x scripts/test.sh" },
],
},
},
});
As implemented in src/CopyToWorktree.ts (lines 33-40), the copy operation completes before runHostHooks executes your commands. This sequence allows you to rebuild native modules or set permissions on copied files before the sandbox starts.
Low-Level API Configuration
When using the low-level createWorktree() API directly, you configure hooks identically through the options parameter:
import { createWorktree } from "sandcastle";
const worktree = await createWorktree({
branchStrategy: { type: "merge-to-head" },
copyToWorktree: ["scripts", "config"],
hooks: {
host: {
onWorktreeReady: [
{ command: "pip install -r requirements.txt", timeoutMs: 120_000 },
{ command: "python scripts/setup.py" },
],
},
},
});
const result = await worktree.run({
agent: claudeCode("claude-3-5-sonnet"),
prompt: "Review the configuration.",
});
This approach is defined in src/createWorktree.ts and follows the same execution order: worktree creation → file copying → hook execution → sandbox readiness.
Technical Implementation Details
Under the hood, runHostHooks (from src/SandboxLifecycle.ts) handles the execution:
- Worktree Creation –
WorktreeManager.createestablishes a temporary git worktree - File Copying – Optional
copyToWorktreepaths are processed - Hook Execution –
runHostHooksreceives the command array and executes each viaexecAsyncwith the configurable timeout - Sandbox Launch – Only after successful hook completion does Sandcastle proceed to
host.onSandboxReadyor start the sandbox container
The current hook types available in src/SandboxLifecycle.ts include:
host.onWorktreeReady– After worktree creation, before sandbox starthost.onSandboxReady– After sandbox container is running, still on the hostsandbox.onSandboxReady– Inside the sandbox container itself
Summary
- Configure lifecycle hooks in Sandcastle by adding a
hooksobject to yourrun()orcreateWorktree()configuration host.onWorktreeReadyexecutes after the git worktree is created and files are copied, but before the sandbox container launches- Commands run sequentially via
runHostHooksinsrc/SandboxLifecycle.ts, with a default 60-second timeout that you can override per command - Combine with
copyToWorktreeto prepare files on the host before running setup commands in the hook - Error handling is strict—any hook failure aborts the entire operation with
ExecErrororHookTimeoutError
Frequently Asked Questions
What is the exact timing of onWorktreeReady relative to the sandbox container?
The onWorktreeReady hook runs before the sandbox container starts. Specifically, it executes after the git worktree is created and any copyToWorktree files are copied, but before Sandcastle initializes Docker, Podman, or bind-mount sandboxes. This timing allows you to modify worktree files or install host-side tools that affect the upcoming sandbox session.
Can I run hooks inside the sandbox container instead of on the host?
Yes, use sandbox.onSandboxReady instead of host.onWorktreeReady. While host.onWorktreeReady and host.onSandboxReady execute on the host machine, sandbox.onSandboxReady runs commands inside the sandbox container itself. This is useful for container-specific setup like installing additional packages or configuring the container environment, whereas host.onWorktreeReady is better for repository preparation.
What happens if an onWorktreeReady command times out?
If a command exceeds its specified timeoutMs (or the default 60,000ms), Sandcastle throws a HookTimeoutError and aborts the entire run() operation. The sandbox container will not start, and the temporary worktree will be cleaned up. You can catch this error to implement fallback logic or increase the timeout for long-running setup scripts.
Can I use shell features like pipes or redirection in hook commands?
No, runHostHooks uses execAsync which executes commands directly without shell interpolation. For complex operations requiring pipes, redirection, or shell variables, create a script file in your repository and call that script from the hook: { command: "./scripts/setup.sh" }. Ensure the script is executable or use an interpreter explicitly: { command: "bash ./scripts/setup.sh" }.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →