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

> Discover how Sandcastle's copyToWorktree uses copy-on-write semantics to efficiently clone host repository paths into worktrees. Learn about reflink support and fallback mechanisms.

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

---

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

### Linux Reflinks

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`](https://github.com/mattpocock/sandcastle/blob/main/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`](https://github.com/mattpocock/sandcastle/blob/main/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`](https://github.com/mattpocock/sandcastle/blob/main/src/createSandbox.ts) and [`src/SandboxFactory.ts`](https://github.com/mattpocock/sandcastle/blob/main/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`](https://github.com/mattpocock/sandcastle/blob/main/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.

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

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

```

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

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