createWorktree vs createSandbox in Sandcastle: Key Differences Explained
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 at lines 85‑86, 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(): Thehost.onWorktreeReadyhook runs after anycopyToWorktreestep completes but before any sandbox exists. If you later callworktree.createSandbox(), thesandbox.onSandboxReadyhook executes only at that instantiation.createSandbox(): Bothhost.onWorktreeReadyandsandbox.onSandboxReadyrun 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 insrc/createWorktree.tslines 87‑102) removes the Git worktree directory unless uncommitted changes exist. Any active sandbox is torn down automatically after eachrun()orinteractive()call completes. - Sandbox cleanup: Calling
sandbox.close()(implemented around lines 70‑77 insrc/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.
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.
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.
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: Implements the deferred sandbox pattern. It creates worktrees viaWorktreeManager.createand returns a handle that lazily initializes sandboxes throughSandboxFactory.src/createSandbox.ts: Orchestrates immediate sandbox provisioning by combining worktree creation withstartSandboxlogic, returning the lower-levelSandboxinterface.src/WorktreeManager.ts: Handles underlying Git operations including worktree creation, pruning, and dirty-state detection.src/Orchestrator.ts: Provides the shared agent execution loop used by bothWorktree.runandSandbox.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, whilesandbox.close()destroys only the container, leaving the worktree intact. - Hook timing varies:
createWorktree()deferssandbox.onSandboxReadyuntil you explicitly create a sandbox, whereascreateSandbox()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.
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.
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 →