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 plaingit pushworks immediately inside the container. - New branch created in the worktree (the typical
--worktree feature/x:mainflow): 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.
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:
Other differences
- Go builds: worktree containers set
GOFLAGS=-buildvcs=falsebecause Go’s VCS stamping cannot work in a linked worktree. Override viaagent.env. See 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.gitread-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.
Creating Worktrees
~/.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 — sogit pull/git pushwork immediately. Pass--no-trackto skip the upstream configuration. When the name matches more than one remote, setcheckout.defaultRemoteto disambiguate. - Otherwise the branch is created from the base ref (default: HEAD).
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.)clawker worktree list to check existing worktrees, or use the --worktree flag on container commands for idempotent “get or create” behavior.
Listing Worktrees
| 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 |
- healthy — Directory,
.gitfile, git metadata, and branch all exist - registry_only — Directory deleted but registry entry remains (safe to prune)
- dotgit_missing — Directory exists but the
.gitfile inside is missing or is a directory instead of a file - git_metadata_missing — Directory and
.gitfile exist, but git’s internal worktree metadata (.git/worktrees/<slug>/) is gone - broken — Directory and git metadata exist, but the branch has been deleted
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
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):
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:
.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.
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.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 for the full explanation of path mirroring and session persistence.
Example: Parallel Feature Development
How Worktrees Interact with the Project Registry
Worktrees are tracked in the project registry (~/.local/share/clawker/registry.yaml) under each project:
/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:
-
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. -
Main
.gitdirectory — The main repository’s.gitdirectory is mounted at its original host absolute path (e.g.,/Users/dev/my-app/.git). This is necessary because the worktree’s.gitfile contains an absolute path reference back to the main repository’s.git/worktrees/<slug>/metadata.
.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 likecore.hooksPath,core.fsmonitor, orfilter.*can execute arbitrary commands; from a worktree,git config --localwrites to this shared file.
git config --local, git remote add, and git push -u fail inside a worktree container (the config file is read-only). 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 for the full behavioral rundown and the post-removal fix.
Go builds inside worktree containers
Worktree containers setGOFLAGS=-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 for the complete explanation of workspace mounting, git integration, and session persistence.