# How to Configure Lifecycle Hooks in Sandcastle: Complete Guide to onWorktreeReady

> Learn to configure Sandcastle lifecycle hooks like onWorktreeReady. Execute commands after worktree creation but before sandbox start with this complete guide.

- Repository: [Matt Pocock/sandcastle](https://github.com/mattpocock/sandcastle)
- Tags: how-to-guide
- Published: 2026-05-24

---

**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`](https://github.com/mattpocock/sandcastle/blob/main/src/interactive.ts)** (lines 279-285) – When running interactive sessions
- **[`src/createWorktree.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/createWorktree.ts)** (lines 241-245) – When using the low-level worktree API
- **[`src/SandboxFactory.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SandboxFactory.ts)** (lines 552-556) – During sandbox factory orchestration

The hook system is defined by the `SandboxHooks` type in [`src/SandboxLifecycle.ts`](https://github.com/mattpocock/sandcastle/blob/main/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:

```typescript
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`](https://github.com/mattpocock/sandcastle/blob/main/src/SandboxLifecycle.ts)), but you can override this per command:

```typescript
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:

```typescript
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`](https://github.com/mattpocock/sandcastle/blob/main/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:

```typescript
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`](https://github.com/mattpocock/sandcastle/blob/main/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`](https://github.com/mattpocock/sandcastle/blob/main/src/SandboxLifecycle.ts)) handles the execution:

1. **Worktree Creation** – `WorktreeManager.create` establishes a temporary git worktree
2. **File Copying** – Optional `copyToWorktree` paths are processed
3. **Hook Execution** – `runHostHooks` receives the command array and executes each via `execAsync` with the configurable timeout
4. **Sandbox Launch** – Only after successful hook completion does Sandcastle proceed to `host.onSandboxReady` or start the sandbox container

The current hook types available in [`src/SandboxLifecycle.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SandboxLifecycle.ts) include:

- **`host.onWorktreeReady`** – After worktree creation, before sandbox start
- **`host.onSandboxReady`** – After sandbox container is running, still on the host
- **`sandbox.onSandboxReady`** – Inside the sandbox container itself

## Summary

- **Configure lifecycle hooks** in Sandcastle by adding a `hooks` object to your `run()` or `createWorktree()` configuration
- **`host.onWorktreeReady`** executes after the git worktree is created and files are copied, but before the sandbox container launches
- **Commands run sequentially** via `runHostHooks` in [`src/SandboxLifecycle.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/SandboxLifecycle.ts), with a default 60-second timeout that you can override per command
- **Combine with `copyToWorktree`** to prepare files on the host before running setup commands in the hook
- **Error handling** is strict—any hook failure aborts the entire operation with `ExecError` or `HookTimeoutError`

## 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" }`.