> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clawker.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Worktrees

> Git worktree integration for multi-branch agent workflows

Clawker integrates with Git worktrees to let you run multiple agents on separate branches simultaneously without conflicts. Each worktree gets its own working directory, and Clawker tracks them in the project registry.

## Why Worktrees?

Without worktrees, running two agents on the same project means they share the same working directory and branch. Changes from one agent can conflict with the other. Worktrees solve this by giving each agent its own checkout of the repository on a separate branch.

Common patterns:

* Run a feature development agent while a review agent works on a different branch
* Test multiple approaches to the same problem in parallel
* Keep the main branch clean while agents experiment on feature branches

## Worktree Caveats

Worktree containers are locked down harder than bind-mode containers, **by design**. They are built for unattended agent sessions (`--dangerously-skip-permissions`), and a worktree shares the main repository's `.git` directory with your host. Anything written to `.git/hooks/` or `.git/config` from inside the container — a planted hook, `core.hooksPath`, `core.fsmonitor`, a `filter.*` driver — would execute **on your host** the next time you run git in the main checkout. To close that vector, both paths are masked **read-only** inside worktree containers. This is a deliberate security measure, always on, and it changes some git behaviors you may take for granted:

| Operation in a worktree container                              | Behavior                                                                               |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `git commit`, `git push`, `git fetch`, `git log`, `git status` | Work normally                                                                          |
| `git config --local`                                           | Fails — config file is read-only                                                       |
| `git remote add`                                               | Fails — writes to the shared config                                                    |
| `git push -u` / `--set-upstream`                               | **Push succeeds**, but the upstream tracking write fails (the warning is easy to miss) |

### Upstream tracking

Upstream tracking lives in the shared `.git/config`, which is read-only inside the container, so where tracking comes from depends on how the branch was born:

* **Branch already existed on the remote** when the worktree was created (e.g. after `git fetch`): tracking is configured host-side at creation, and a plain `git push` works immediately inside the container.
* **New branch created in the worktree** (the typical `--worktree feature/x:main` flow): no upstream exists, and it cannot be set from inside the container. Push with an explicit refspec instead — `git push origin HEAD` — and PR creation (`gh pr create`) works fine.

After you remove the worktree and check the branch out on the host, a plain `git push` will report there is no upstream — even though the branch was pushed and has an open PR. Tracking was never persisted; set it once on the host:

```bash theme={"dark"}
git push -u origin <branch>     # or: git branch --set-upstream-to=origin/<branch>
```

### Other differences

* **Go builds**: worktree containers set `GOFLAGS=-buildvcs=false` because Go's VCS stamping cannot work in a linked worktree. Override via `agent.env`. See [Go builds inside worktree containers](#go-builds-inside-worktree-containers).
* **Branch-keyed, no detached HEAD**: clawker worktrees are identified by branch name; detached-HEAD worktrees are unsupported.
* **Bind mode only**: worktrees require `workspace.default_mode: bind` (or `--mode bind`). They are rejected in snapshot mode — a worktree binds the host's main `.git` read-write, and a snapshot copy on top of that would let in-container writes reach your host repo, defeating snapshot isolation. Snapshot mode already isolates the workspace from the host on its own, so the two are mutually exclusive.

The lockdown is currently not configurable — secure by default. A policy gate to relax it per project is planned.

## Creating Worktrees

```bash theme={"dark"}
# Create a worktree for a new branch (branches from HEAD)
clawker worktree add feature/auth

# Create from a specific base branch
clawker worktree add feature/cache --base main

# Branch names with slashes are supported
clawker worktree add hotfix/login-redirect

# Check out a fetched remote branch with upstream tracking
git fetch origin dependabot/go_modules/foo
clawker worktree add dependabot/go_modules/foo

# ...or create the branch without tracking the remote
clawker worktree add dependabot/go_modules/foo --no-track
```

The worktree directory is created under `~/.local/share/clawker/worktrees/<repoName>-<projectName>-<sha256(uuid)[:12]>/`.

Branch resolution mirrors native `git worktree add` (no network is performed):

* If the branch already exists locally, it's checked out in the new worktree.
* If it doesn't exist locally but a **remote-tracking branch** matching the name exists in exactly one remote (e.g. after `git fetch`), the branch is created from the remote tip and set up to **track** it — so `git pull`/`git push` work immediately. Pass `--no-track` to skip the upstream configuration. When the name matches more than one remote, set `checkout.defaultRemote` to disambiguate.
* Otherwise the branch is created from the base ref (default: HEAD).

<Note>
  Pass the **bare** branch name (`fix/login`), not an `origin/`-prefixed ref. Clawker worktrees are identified by branch, so an explicit remote-tracking ref that exists (e.g. `origin/fix/login` after `git fetch`) is rejected with a hint — pass the bare `fix/login` and it is created from the remote tip with tracking. (Unlike `git worktree add origin/fix/login`, which detaches HEAD; clawker's branch-keyed worktrees do not support detached HEAD.)
</Note>

If the worktree already exists in the registry, the command returns an error. Use `clawker worktree list` to check existing worktrees, or use the `--worktree` flag on container commands for idempotent "get or create" behavior.

## Listing Worktrees

```bash theme={"dark"}
# Full table view
clawker worktree list

# Branch names only
clawker worktree ls -q
```

The table shows:

| Column   | Description                                                                                 |
| -------- | ------------------------------------------------------------------------------------------- |
| BRANCH   | Branch name ("(detached)" if HEAD was manually detached inside the worktree after creation) |
| PATH     | Filesystem path to worktree                                                                 |
| HEAD     | Short commit hash                                                                           |
| MODIFIED | Relative time since last change                                                             |
| STATUS   | Health status                                                                               |

Status values:

* **healthy** — Directory, `.git` file, git metadata, and branch all exist
* **registry\_only** — Directory deleted but registry entry remains (safe to prune)
* **dotgit\_missing** — Directory exists but the `.git` file inside is missing or is a directory instead of a file
* **git\_metadata\_missing** — Directory and `.git` file exist, but git's internal worktree metadata (`.git/worktrees/<slug>/`) is gone
* **broken** — Directory and git metadata exist, but the branch has been deleted

Locked worktrees (via `git worktree lock`) have `IsLocked` set internally and are protected from pruning, but lock status is independent of health — a locked worktree can have any status.

## Removing Worktrees

````bash theme={"dark"}
# Remove a worktree
clawker worktree remove feature/auth

# Remove multiple worktrees
clawker worktree rm feature/auth feature/cache

# Also delete the branch\nclawker worktree remove --delete-branch feature/auth\n```

Safety checks prevent accidental data loss:
- Refuses to remove worktrees with uncommitted changes
- `--delete-branch` skips branch deletion when the branch has unmerged commits, prints a warning, and suggests `git branch -D` for force deletion — the worktree itself is still removed
- `--delete-branch` returns an error when asked to delete the currently checked-out branch (`git.ErrIsCurrentBranch`) — the worktree is still removed

## Health Checks

When you list or inspect worktrees, Clawker performs a multi-layer health check on each entry:

1. **Directory existence** — Does the worktree directory still exist on disk?
2. **`.git` file check** — Linked worktrees contain a `.git` *file* (not a directory) that points back to the main repository's `.git/worktrees/<slug>/` metadata. Clawker verifies this file exists and is indeed a file.
3. **Git metadata check** — The main repository's `.git/worktrees/<slug>/` directory must exist. This is where git stores the worktree's HEAD, index, and config.
4. **Branch existence** — The branch associated with the worktree must still exist in the repository.
5. **Lock detection** — Checks for a `locked` file in the worktree's git metadata. Locked worktrees are protected from pruning.

If any check fails, the worktree's status reflects the first failure found. When a check can't be performed (e.g., the git manager isn't available), Clawker degrades gracefully — it reports what it can and notes the limitation in the inspect error.

## Pruning Stale Entries

If worktree directories are deleted manually or by `git worktree remove` (bypassing Clawker), registry entries become stale. Prune them:

```bash
# Preview what would be pruned
clawker worktree prune --dry-run

# Remove stale entries
clawker worktree prune
````

A worktree is considered prunable when any of: the directory is missing, git metadata is gone, or the branch has been deleted. However, **locked worktrees are never pruned** — even if they appear stale. If a worktree has been locked with `git worktree lock`, it's skipped during pruning and reported separately in the output.

## Automatic Worktree Creation via `--worktree`

Container commands accept a `--worktree` flag that automatically creates or reuses a worktree. It is a happy-path shortcut: like `clawker worktree add` it checks out a matching remote-tracking branch with upstream when one exists, but it does not expose the full flag surface (use `clawker worktree add` for options such as `--no-track`):

```bash theme={"dark"}
# Container commands
clawker run --worktree feature/auth @
clawker container run --worktree feature/cache @
clawker container create --worktree hotfix/login @

# Create branch from a specific base
clawker run --worktree hotfix/login:main @
```

The flag syntax is `branch` or `branch:base`. If the branch doesn't exist, it's created from the base ref (default: HEAD). If it already exists, it's checked out in the worktree.

### Idempotent behavior

The `--worktree` flag is **idempotent** -- if the worktree already exists and is healthy, it is reused rather than recreated. This means you can run the same command repeatedly without error:

```bash theme={"dark"}
# First run: creates the worktree
clawker run --worktree feature/auth @

# Second run: reuses the existing worktree
clawker run --worktree feature/auth @
```

If the worktree exists in the registry but is unhealthy (directory deleted, `.git` file missing, git metadata gone, or branch deleted), the command fails with a message suggesting `clawker worktree prune` to clean up the stale entry first. Only worktrees with a `healthy` status are accepted for reuse.

<Note>
  This differs from `clawker worktree add`, which is a strict creation command — it errors if the worktree already exists. The `--worktree` flag on container commands is designed for repeated use in workflows where you want "get or create" semantics.
</Note>

### Path mirroring

When `--worktree` is used, the worktree directory is mounted into the container at its **host absolute path** — not at a generic `/workspace`. This is essential for session persistence: Claude Code discovers sessions by matching the container's current working directory against git worktree paths. If the container used a synthetic path, session resume would fail because the paths wouldn't match.

See [Container Internals](/container-internals) for the full explanation of path mirroring and session persistence.

## Example: Parallel Feature Development

```bash theme={"dark"}
# Terminal 1: Feature A on its own branch
clawker run --worktree feature/auth @

# Terminal 2: Feature B in parallel
clawker run --worktree feature/rate-limit @

# Terminal 3: Check on both
clawker worktree list

# When done, clean up
clawker worktree remove --delete-branch feature/auth
clawker worktree remove --delete-branch feature/rate-limit
```

## How Worktrees Interact with the Project Registry

Worktrees are tracked in the project registry (`~/.local/share/clawker/registry.yaml`) under each project:

```yaml theme={"dark"}
projects:
  - name: "my-app"
    root: "/Users/dev/my-app"
    worktrees:
      feature/auth:
        path: "/Users/dev/.local/share/clawker/worktrees/my-app-my-app-a1b2c3d4e5f6"
        branch: "feature/auth"
```

This allows Clawker to manage worktrees across sessions and detect stale entries.

All worktree operations use the project root from the registry record rather than re-resolving from the filesystem. This avoids subtle path mismatches on systems where paths can differ between resolutions (e.g., macOS symlinks like `/var` → `/private/var`). The registry is the single source of truth for project identity.

## How Worktrees Work Inside Containers

When a container is created with `--worktree`, two mounts are set up:

1. **Worktree directory** — The worktree's checkout directory is mounted at its host absolute path (e.g., `~/.local/share/clawker/worktrees/my-app-my-app-abc123def456/`). This becomes the container's working directory.

2. **Main `.git` directory** — The main repository's `.git` directory is mounted at its original host absolute path (e.g., `/Users/dev/my-app/.git`). This is necessary because the worktree's `.git` file contains an absolute path reference back to the main repository's `.git/worktrees/<slug>/` metadata.

By mounting both at their real host paths, git commands inside the container work correctly — the `.git` file's reference resolves, git finds the shared metadata, and operations like `git log`, `git status`, and `git commit` behave normally.

### Protected `.git` paths

The main `.git` mount is read-write (commits from the worktree write objects, refs, and per-worktree metadata there), but two paths inside it are masked with **read-only** binds:

* `.git/hooks/` — a hook planted from inside the container would execute on the *host* the next time you run git in the main checkout.
* `.git/config` — config keys like `core.hooksPath`, `core.fsmonitor`, or `filter.*` can execute arbitrary commands; from a worktree, `git config --local` writes to this shared file.

This keeps worktree containers meaningfully more isolated than bind mode. The trade-off: `git config --local` and `git remote add` fail inside a worktree container (the config file is read-only), and `git push -u` still pushes the branch but can't persist upstream tracking (the warning is easy to miss). Upstream tracking is configured host-side at creation **only when the branch already existed on the remote**; branches born in the worktree have no upstream — push with `git push origin HEAD`. See [Worktree Caveats](#worktree-caveats) for the full behavioral rundown and the post-removal fix.

### Go builds inside worktree containers

Worktree containers set `GOFLAGS=-buildvcs=false`. Go's VCS stamping cannot work in a linked worktree: the `go` tool only recognizes a `.git` *directory* (a worktree has a `.git` file), so it walks up to the mounted main `.git`, where stamping would either fail (`error obtaining VCS status: exit status 128`) or record the wrong revision. Set `GOFLAGS` in `agent.env` to override.

See [Container Internals](/container-internals) for the complete explanation of workspace mounting, git integration, and session persistence.
