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:
.git object store. Creating a worktree takes milliseconds because Git simply writes a lightweight .git/worktrees/<id> in the main repository, avoiding the disk and network overhead of cloning full repository copies.index), HEAD ref pointer, and reflog. This allows multiple agent sessions to run in parallel on different feature branches without encountering lock contention or file collision.
The shared object store is what makes a worktree cheap, and it is also what makes it stop at the repository it belongs to.
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.
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:
feature-auth inside main inside Using raw git worktree commands manually requires executing a tedious multi-step sequence across three separate repository paths:
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
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/docs
Executing these steps manually for every feature task is slow, repetitive, and error-prone. Consequently, developers build shell scripts to automate multi-repository worktree assembly.
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
echo "Creating reference worktree for $NAME on main..."
git -C "$REPO_PATH" worktree add "$TARGET_WORKTREE" main 2>/dev/null || true
else
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
ln -sfn "$TARGET_WORKTREE" "$WORKSPACE_DIR/$NAME"
done
cat <<EOF > "$WORKSPACE_DIR/CLAUDE.md"
- 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" && claude
This 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.
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 |
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.
When an agent session finishes or crashes, the script leaves active worktrees and branches dangling across multiple repository directories. If a session fails midway:
.git/worktrees/ across individual repos.git worktree prune and 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.
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.
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.
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:
docs/) is mounted into the session workspace, write bits ( 0o222) are stripped recursively across its worktree files and directories via clear_write_bits.>), or an automated code modifier—the operating system kernel rejects the write operation immediately with an EACCES (Permission Denied) error.u+w) via restore_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.