# createWorktree vs createSandbox in Sandcastle: Key Differences Explained

> Understand the key differences between createWorktree and createSandbox in Sandcastle. Learn how each function manages Git worktrees and sandbox execution for your projects.

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

---

**`createWorktree()` returns a `Worktree` handle that decouples Git worktree management from sandbox execution, while `createSandbox()` returns a `Sandbox` handle that immediately binds a Git worktree to an isolated container environment.**

Choosing between these two entry points in the `mattpocock/sandcastle` ecosystem determines whether you need a persistent worktree that survives multiple sandbox sessions or a single-use execution environment. Both functions manage Git worktrees, but they differ fundamentally in lifecycle coupling, return types, and cleanup semantics.

## Core Architectural Differences

### Primary Purpose and Return Types

**`createWorktree()`** creates a Git worktree—a lightweight checkout of a branch—and returns a `Worktree` handle. This handle exposes `run()`, `interactive()`, and `createSandbox()` methods, allowing you to decide later whether to execute agents inside a sandbox or directly on the host using `noSandbox()`.

**`createSandbox()`**, implemented in [`src/createSandbox.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/createSandbox.ts) at lines [85‑86](https://github.com/mattpocock/sandcastle/blob/main/src/createSandbox.ts#L85-L86), immediately creates both a Git worktree *and* a sandbox (container, bind‑mount, or isolated provider). It returns a `Sandbox` handle with `run()`, `interactive()`, and `close()` methods, where the underlying worktree remains intact even after the sandbox container is destroyed.

### Lifecycle and Hook Execution

The timing of lifecycle hooks diverges significantly between the two approaches:

- **`createWorktree()`**: The `host.onWorktreeReady` hook runs after any `copyToWorktree` step completes but **before** any sandbox exists. If you later call `worktree.createSandbox()`, the `sandbox.onSandboxReady` hook executes only at that instantiation.
- **`createSandbox()`**: Both `host.onWorktreeReady` and `sandbox.onSandboxReady` run during initial creation, with the sandbox hook firing immediately after the container starts.

File copy operations also differ. With `createWorktree()`, `copyToWorktree` occurs once during worktree creation (or later when calling `createSandbox()` for bind‑mount providers). With `createSandbox()`, the copy happens immediately during initialization, though isolated providers handle file copying internally.

### Close Semantics and Resource Management

Cleanup behavior represents the most critical operational difference:

- **Worktree cleanup**: Calling `worktree.close()` (defined in [`src/createWorktree.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/createWorktree.ts) lines [87‑102](https://github.com/mattpocock/sandcastle/blob/main/src/createWorktree.ts#L87-L102)) removes the Git worktree directory unless uncommitted changes exist. Any active sandbox is torn down automatically after each `run()` or `interactive()` call completes.
- **Sandbox cleanup**: Calling `sandbox.close()` (implemented around lines [70‑77](https://github.com/mattpocock/sandcastle/blob/main/src/createSandbox.ts#L70-L77) in [`src/createSandbox.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/createSandbox.ts)) shuts down only the container process, leaving the Git worktree on disk for inspection or reuse.

## When to Use createWorktree()

Use **`createWorktree()`** when you need a persistent worktree that outlives individual sandbox sessions. This approach suits workflows where you want to inspect the filesystem after agent execution, run multiple agents against the same checkout, or defer the decision about which sandbox provider to use.

```typescript
import { createWorktree, docker } from "sandcastle";

// Create a persistent worktree on a new branch
const wt = await createWorktree({
  branchStrategy: { type: "branch", branch: "feature-xyz" },
  cwd: "/path/to/repo",
});

// Run first agent in Docker sandbox
await wt.run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker({ imageName: "sandcastle:myrepo" }),
  prompt: "Improve the README.md file",
});

// Reuse the same worktree with a different sandbox configuration
const sb = await wt.createSandbox({
  sandbox: docker({ imageName: "sandcastle:other" }),
});

await sb.run({ agent, prompt: "Refactor utils.ts" });
await sb.close(); // Removes only the container

// Clean up the worktree when completely done
await wt.close(); // Removes the worktree (unless dirty)

```

The `Worktree` interface explicitly supports this lazy sandbox attachment pattern, as shown in the `worktree.run` implementation at lines [72‑74](https://github.com/mattpocock/sandcastle/blob/main/src/createWorktree.ts#L72-L74).

## When to Use createSandbox()

Use **`createSandbox()`** when you require immediate isolation and do not need to reuse the worktree beyond the current session. This API bundles `WorktreeManager.create` with `startSandbox` into a single operation, providing a handle that encapsulates both the Git checkout and the execution environment.

```typescript
import { createSandbox, podman } from "sandcastle";

const sb = await createSandbox({
  branch: "feature-abc",
  sandbox: podman({ imageName: "sandcastle:myrepo" }),
  cwd: "/my/project",
  copyToWorktree: ["src/", "package.json"], // Bind-mount specific files
});

// Execute agent inside the sandbox
await sb.run({
  agent: claudeCode("claude-opus-4-7"),
  prompt: "Add unit tests for the new feature",
});

// Close only the container; worktree remains for debugging
await sb.close();

```

Because `createSandbox()` requires the `sandbox` option during initialization, it guarantees that every execution occurs within the specified isolation boundary from the first `run()` call.

## Implementation Details

Both functions rely on shared infrastructure but expose different abstraction levels:

- **[`src/createWorktree.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/createWorktree.ts)**: Implements the deferred sandbox pattern. It creates worktrees via `WorktreeManager.create` and returns a handle that lazily initializes sandboxes through `SandboxFactory`.
- **[`src/createSandbox.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/createSandbox.ts)**: Orchestrates immediate sandbox provisioning by combining worktree creation with `startSandbox` logic, returning the lower-level `Sandbox` interface.
- **[`src/WorktreeManager.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/WorktreeManager.ts)**: Handles underlying Git operations including worktree creation, pruning, and dirty-state detection.
- **[`src/Orchestrator.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/Orchestrator.ts)**: Provides the shared agent execution loop used by both `Worktree.run` and `Sandbox.run`, managing iterations, commits, and completion signals regardless of which entry point initiated the session.

## Summary

- **Use `createWorktree()`** when you need a **persistent Git worktree** that can survive multiple sandbox sessions or remain available for post-execution inspection.
- **Use `createSandbox()`** when you need an **immediate isolated environment** and prefer a single handle that manages both the worktree and container lifecycle.
- **Close semantics differ**: `worktree.close()` destroys the Git checkout, while `sandbox.close()` destroys only the container, leaving the worktree intact.
- **Hook timing varies**: `createWorktree()` defers `sandbox.onSandboxReady` until you explicitly create a sandbox, whereas `createSandbox()` executes it immediately during initialization.

## Frequently Asked Questions

### Can I convert a Worktree into a Sandbox after creation?

Yes. The `Worktree` handle returned by `createWorktree()` includes a `createSandbox()` method that instantiates a sandbox provider and returns a `Sandbox` handle. This allows you to transition from host-side worktree management to containerized execution without recreating the Git checkout, as implemented in [`src/createWorktree.ts`](https://github.com/mattpocock/sandcastle/blob/main/src/createWorktree.ts).

### Does `createSandbox()` delete the worktree when I call `close()`?

No. Calling `close()` on a `Sandbox` handle only terminates the container process. The underlying Git worktree remains on disk, allowing you to inspect files, review agent changes, or manually clean up later. To remove the worktree explicitly, you must use `createWorktree()` and call `worktree.close()` instead.

### Which API should I choose for CI/CD pipelines?

**`createSandbox()`** is typically preferable for CI/CD workflows because it ensures immediate sandbox isolation and provides a single cleanup boundary (`sandbox.close()`). However, if your pipeline requires running multiple agents against the same checkout or preserving the worktree for artifact collection between steps, **`createWorktree()`** offers the necessary persistence.

### What happens to uncommitted changes when closing a Worktree?

`worktree.close()` checks for uncommitted changes before removal. If dirty files exist, the cleanup operation typically aborts or warns (depending on the `WorktreeManager` implementation), preventing accidental data loss. This safety mechanism does not apply to `sandbox.close()`, which only manages container lifecycle and ignores Git state.