How Does copyToWorktree Work in Sandcastle? Copy-on-Write Preloading Explained

The copyToWorktree utility efficiently copies host repository paths into Sandcastle's temporary Git worktrees using platform-specific copy-on-write (CoW) semantics, falling back to standard recursive copies when the filesystem lacks reflink support.

Sandcastle, an open-source sandboxing tool by mattpocock/sandcastle, isolates development environments using ephemeral Git worktrees. The copyToWorktree feature accelerates sandbox startup by pre-loading large, immutable directories—such as node_modules—from the host repository into the isolated worktree without duplicating disk blocks.

Copy-on-Write Flag Selection

The implementation begins by determining the appropriate CoW flags for the underlying operating system. Inside src/CopyToWorktree.ts, the getCopyOnWriteFlags function (lines 14-20) returns platform-specific arguments for the cp command.

macOS APFS Cloning

On macOS, the utility leverages APFS clone-file capabilities via the -c flag. The command cp -cR creates cheap copies that share blocks with the source files, consuming negligible additional disk space.

On Linux and other platforms, the implementation uses cp -R --reflink=auto to request a reflink copy. This asks GNU coreutils to perform a copy-on-write operation when the underlying filesystem supports it, such as on Btrfs or XFS with reflink enabled.

The Copy Execution Pipeline

The copyToWorktree function in src/CopyToWorktree.ts orchestrates the file transfer through a series of effectful operations.

Path Resolution and Validation

For each relative path provided by the user, the utility constructs absolute source and destination paths. It maps src to <hostRepoDir>/<path> and dest to <worktreePath>/<path>. An existsSync guard (lines 35-39) silently ignores missing source paths, allowing the sandbox to start even if certain optional directories are absent.

Asynchronous Execution with Fallback

The copy operation executes within an Effect.async block, making it composable with other sandbox effects. The system first attempts the CoW copy using the platform-specific flags. If this fails—typically because the filesystem does not support reflinks—the implementation falls back to a standard cp -R command (lines 42-64).

Error Handling and Timeout Boundaries

Robust error handling ensures sandbox creation fails gracefully when file operations go wrong.

When the fallback copy fails, the utility wraps the error in a CopyToWorktreeError (lines 46-58). This error capture includes the specific path, stderr output, and exit code from the failed cp process.

The entire effect is wrapped with withTimeout using a default deadline of 60 seconds (COPY_TO_WORKTREE_TIMEOUT_MS). If the operation exceeds this limit, the system raises a CopyToWorktreeTimeoutError containing the list of paths that failed to copy (lines 71-79). Users can override this timeout via options.timeouts?.copyToWorktreeMs.

Integration with Sandcastle Workflows

The copyToWorktree utility integrates at multiple points in the sandbox lifecycle to ensure files are present before containers start.

Worktree Creation Hooks

In src/createWorktree.ts, the function invokes copyToWorktree immediately after a worktree is created but before any onWorktreeReady hooks execute (lines 233-241). This sequencing ensures dependencies are available for hook scripts that might need them.

Sandbox Factory Propagation

Both src/createSandbox.ts and src/SandboxFactory.ts propagate the copyToWorktree option through their respective creation pipelines (lines 528-537). This ensures the preloading step executes consistently whether creating worktrees directly or through the higher-level sandbox factory.

CLI Validation Rules

The interactive CLI in src/interactive.ts enforces a mutual exclusivity rule: the copyToWorktree option cannot be combined with the "head" branch strategy (lines 137-144). Since the head strategy operates directly on the host repository rather than a temporary worktree, pre-loading files would be redundant and potentially destructive.

Configuration and Usage Examples

Users configure copyToWorktree via the CLI or configuration files by providing an array of relative paths.

import { copyToWorktree } from "./CopyToWorktree.js";

const paths = ["node_modules", ".env.example"];
const hostRepoDir = "/home/user/project";
const worktreePath = "/tmp/sandcastle-worktree";

await copyToWorktree(paths, hostRepoDir, worktreePath)()
  .catch((e) => console.error("Copy failed:", e));

To customize the operation timeout, pass a millisecond value as the fourth argument:

await copyToWorktree(
  ["large-assets"],
  hostRepoDir,
  worktreePath,
  30_000 // 30 seconds
)()

When using the higher-level worktree creation API, specify paths in the options object:

import { createWorktree } from "./createWorktree.js";

await createWorktree({
  hostRepoDir,
  worktreePath,
  copyToWorktree: ["node_modules"],
  timeouts: { copyToWorktreeMs: 45_000 },
})()

Summary

  • The copyToWorktree feature uses platform-specific CoW flags (cp -cR on macOS, cp -R --reflink=auto on Linux) to minimize disk usage when preloading directories.
  • Implementation resides in src/CopyToWorktree.ts and handles missing paths gracefully while providing automatic fallback to standard copies when reflinks fail.
  • Operations are bounded by a 60-second default timeout (COPY_TO_WORKTREE_TIMEOUT_MS) and return structured errors (CopyToWorktreeError, CopyToWorktreeTimeoutError) on failure.
  • The utility integrates into worktree creation, sandbox factory, and interactive CLI workflows, with validation preventing misuse with the "head" branch strategy.

Frequently Asked Questions

What happens if the filesystem doesn't support copy-on-write?

If the initial CoW command fails, Sandcastle automatically falls back to a standard recursive copy (cp -R) as implemented in src/CopyToWorktree.ts (lines 42-64). Only if this fallback fails does the system throw a CopyToWorktreeError.

Can I use copyToWorktree with the "head" branch strategy?

No. The interactive CLI explicitly validates that copyToWorktree cannot be used with the head branch strategy (lines 137-144 in src/interactive.ts). Since the head strategy operates directly on the host repository rather than a temporary worktree, copying files would be unnecessary.

How do I configure the timeout for copyToWorktree operations?

Pass a custom timeout value via options.timeouts?.copyToWorktreeMs when calling createWorktree or createSandbox, or provide a millisecond value as the fourth argument to the copyToWorktree function directly. The default timeout is 60 seconds.

Does copyToWorktree fail if a specified path doesn't exist?

No. The implementation uses an existsSync guard (lines 35-39) to silently ignore missing source paths. This allows sandbox creation to proceed even if optional directories like .env.example are not present in the host repository.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →