Git worktrees are great. They stop at the repo boundary.
What the worktree posts get right
Git worktrees (git worktree add) have become an indispensable primitive in modern software engineering and agentic workflows. Most community articles explaining how to use git worktrees for AI coding agents get the core single-repository mechanics completely right:
- Zero Stash and Context Switch Overhead: You can launch an agentic session on an isolated feature branch without stashing uncommitted local work or altering your active working directory.
- Shared Object Database: Worktrees share the parent repository's
.gitobject store. Creating a worktree takes milliseconds because Git simply writes a lightweight.gittext file pointing to.git/worktrees/<id>in the main repository, avoiding the disk and network overhead of cloning full repository copies. - Independent Index Files: Each worktree maintains its own independent staging area (
index),HEADref pointer, and reflog. This allows multiple agent sessions to run in parallel on different feature branches without encountering lock contention or file collision.
For single-repository engineering, git worktrees provide an ideal local execution environment. The agent operates inside an isolated file tree, language servers index files correctly, build tools run natively, and git status tracks changes cleanly.
However, modern software applications rarely reside in a single repository. The moment a software feature spans multiple repositories, the single-repo worktree model encounters a hard structural boundary.
The three-repo change
Consider a routine architectural task in a polyrepo ecosystem: modifying an API contract. The feature spans three distinct git repositories:
api-service: The Rust backend implementing the updated endpoint payload.web-frontend: The TypeScript application consuming the API response.docs-site: The documentation repository hosting public API reference specs.
To execute this change with an AI coding agent, you require a composite workspace configuration:
- Active edit capability on branch
feature-authinsideapi-service. - Active edit capability on branch
feature-authinsideweb-frontend. - Read-only reference access on default branch
maininsidedocs-site.
Using raw git worktree commands manually requires executing a tedious multi-step sequence across three separate repository paths:
# Manual worktree creation across three repos
git -C ~/repos/api-service worktree add -b feature-auth ../worktrees/api-feature-auth
git -C ~/repos/web-frontend worktree add -b feature-auth ../worktrees/web-feature-auth
git -C ~/repos/docs-site worktree add ../worktrees/docs-main main
# Create a temporary workspace directory and symlink worktrees
mkdir -p ~/workspaces/feature-auth
ln -s ~/worktrees/api-feature-auth ~/workspaces/feature-auth/api
ln -s ~/worktrees/web-feature-auth ~/workspaces/feature-auth/web
ln -s ~/worktrees/docs-main ~/workspaces/feature-auth/docsExecuting these steps manually for every feature task is slow, repetitive, and error-prone. Consequently, developers build shell scripts to automate multi-repository worktree assembly.
Writing the script anyway
Here is a typical, 60-line bash script that developers construct to automate multi-repository worktree setup for AI agent sessions:
#!/usr/bin/env bash
set -euo pipefail
FEATURE_NAME="${1:-}"
if [[ -z "$FEATURE_NAME" ]]; then
echo "Error: Feature name required."
echo "Usage: $0 <feature-name>"
exit 1
fi
WORKSPACE_DIR="$HOME/.workspaces/$FEATURE_NAME"
mkdir -p "$WORKSPACE_DIR"
declare -A REPOS=(
["api"]="$HOME/repos/api-service"
["web"]="$HOME/repos/web-frontend"
["docs"]="$HOME/repos/docs-site"
)
echo "Initializing multi-repo worktrees for feature: $FEATURE_NAME"
for NAME in "${!REPOS[@]}"; do
REPO_PATH="${REPOS[$NAME]}"
TARGET_WORKTREE="$REPO_PATH/../worktrees/$NAME-$FEATURE_NAME"
if [[ "$NAME" == "docs" ]]; then
# Read-only reference repository stays checked out on main
echo "Creating reference worktree for $NAME on main..."
git -C "$REPO_PATH" worktree add "$TARGET_WORKTREE" main 2>/dev/null || true
else
# Feature repositories check out feature branch
echo "Creating feature worktree for $NAME on branch $FEATURE_NAME..."
git -C "$REPO_PATH" worktree add -b "$FEATURE_NAME" "$TARGET_WORKTREE" 2>/dev/null || true
fi
# Symlink worktree into unified workspace folder
ln -sfn "$TARGET_WORKTREE" "$WORKSPACE_DIR/$NAME"
done
# Create combined harness instructions
cat <<EOF > "$WORKSPACE_DIR/CLAUDE.md"
# Multi-Repo Workspace Context: $FEATURE_NAME
- api/: Rust backend service (editable feature worktree)
- web/: TypeScript web application (editable feature worktree)
- docs/: Documentation site (reference context only, DO NOT EDIT)
EOF
echo "Multi-repo workspace materialized at: $WORKSPACE_DIR"
cd "$WORKSPACE_DIR" && claudeThis script automates worktree creation, symlinks the directories into a unified workspace folder, generates a basic CLAUDE.md, and launches the agent harness.
While this script works for basic local demos, attempting to rely on it in production engineering environments reveals four fundamental limitations.
Four things the script can't do
While an ad-hoc shell script automates directory symlinking, it lacks the technical infrastructure required for reliable, safe multi-repository agent execution.
| What it needs to do | What the script does instead |
|---|---|
| Guard writes to reference repos | Relies on a soft prompt rule |
| Keep branches and lifecycle aligned | Leaves dirty and orphaned trees |
| Materialise instructions | Dirties tracked files in each repo |
| Deliver the feature | Pushes each branch on its own |
1. POSIX Filesystem Write Protection
The shell script creates worktrees on disk using standard user write permissions (0755/0644). To restrict modifications in docs/, the script relies exclusively on a text instruction inside CLAUDE.md: "docs/: reference context only, DO NOT EDIT".
Under high context load or multi-step reasoning tasks, language models non-deterministically violate prompt instructions. An agent fixing a broken build or lint error will edit files in docs/ directly, mutating git state in a reference repository. The script cannot enforce write restrictions at the operating system layer.
2. Session Lifecycle and Branch Alignment
When an agent session finishes or crashes, the script leaves active worktrees and branches dangling across multiple repository directories. If a session fails midway:
- Worktree lock references persist inside
.git/worktrees/across individual repos. - Re-running the script throws errors because branches or worktree paths already exist.
- Developers must manually navigate to each repo directory to run
git worktree pruneand delete orphaned feature branches.
Furthermore, if branch creation fails in one repository (e.g., due to an uncommitted conflict on main), the script leaves the workspace in a partially initialized, inconsistent state across repos without atomic rollback.
3. Clean Instruction Materialization
The script injects instructions by writing a static CLAUDE.md file at the root of the workspace. However, real-world repositories already contain their own provider-native instruction files (e.g., api/CLAUDE.md and web/CLAUDE.md). The script cannot merge or synthesize instructions across repositories.
If the script writes instruction files directly inside individual repository worktrees, it leaves untracked, dirty files in git status that risk being accidentally committed by the agent.
4. Multi-Repo Feature Delivery
Once an agent completes changes across api/ and web/, delivering the feature requires verifying status, pushing feature branches across multiple repositories, and establishing cross-repository pull request references.
The shell script leaves delivery entirely to manual git operations. It cannot verify atomic feature readiness across mounted repositories, track which repositories were actually modified during the session, or trigger synchronized multi-repo branch pushes (deliver). Note that multi-repo delivery coordinates push operations across repositories, rather than attempting complex dependency sorting or automated merge ordering.
Guarding writes with chmod, not with a prompt
The central vulnerability of custom multi-repo worktree scripts is relying on LLM prompt compliance for write protection. Soft prompt instructions and software hooks inevitably fail under context saturation, complex agent reasoning, or raw shell command execution.
Robust write protection requires enforcing file immutability at the operating system layer using POSIX file permission masks (chmod).
if mode & 0o222 != 0 {
chmod(path, mode & !0o222)?;
}In an orchestrated View Directory architecture:
- Unpromoted Context Worktrees: When a reference repository (such as
docs/) is mounted into the session workspace, write bits (0o222) are stripped recursively across its worktree files and directories viaclear_write_bits. - Deterministic Kernel Enforcement: If an agent attempts to modify a file inside an unpromoted repository—whether using a file edit tool, a bash output redirection (
>), or an automated code modifier—the operating system kernel rejects the write operation immediately with anEACCES(Permission Denied) error. - Promoted Worktrees: Repositories explicitly designated for active editing have write permissions restored (
u+w) viarestore_write_bits(mode | 0o200), permitting authorized modifications:
chmod(path, mode | 0o200)?;By operating at the kernel permission layer, write enforcement remains absolute regardless of LLM prompt drift, context saturation, or tool execution style.
Git worktrees provide the essential foundation for local agent workspaces, but scaling them across multiple repositories requires dedicated session orchestration rather than fragile shell scripts. How a View Dir combines those worktrees with a kernel-enforced write guard is covered in what a View Dir is, and the quickstart walks through mounting your first one.