Zoom-in: Git Worktree

Switching branches to fix a hotfix when your current working directory is dirty is a common developer pain point. The typical options are to create a messy temporary commit (with messages like "wip" or "temp") or use git stash to shelve unfinished changes — which are then easily forgotten or prone to conflicts when popped.
Fortunately, there is a much cleaner way: creating a separate, parallel working directory pointing to the same repository using git worktree. This is especially powerful in the current era of autonomous AI coding agents (such as Claude Code and Antigravity) that are deeply integrated into daily workflows. A developer can easily assign a build task or testing suite to an agent on a separate branch inside an independent worktree running in the background, while continuing to code in the main directory without any IDE conflicts or interruptions.
A concrete example: you're mid-way through feature A in the myapp directory (on main), but you need Claude Code to run the full test suite for a different pull request on feature/checkout-flow. Instead of stashing feature A, just spin up a separate worktree for the agent:
git worktree add ../myapp-agent-review feature/checkout-flow
cd ../myapp-agent-review && claude-code run-tests
The agent reads and writes files freely inside ../myapp-agent-review without touching the staging area or HEAD of the main myapp directory. Once it's done, clean up with git worktree remove ../myapp-agent-review.
Zooming in on that.
Layer 1 — Independent Working Tree and the Flat .git File
A standard Git repository has only one working tree tied to a single .git metadata folder. To work on multiple branches simultaneously without disturbing your current folder, Git needs a mechanism to isolate the source code in another directory while maintaining a connection to the central database.
When running git worktree add ../hotfix-branch hotfix, Git creates a new directory at ../hotfix-branch and checks out the hotfix branch there. This new directory is a completely independent working tree.
Instead of copying the entire, massive .git/ folder, Git creates a small, flat text file named .git in the new directory. This file contains a single line pointing to the actual metadata directory housed inside the main repository:
gitdir: /path/to/main-repo/.git/worktrees/hotfix-branch
This flat .git file acts as a special symlink for Git, telling any git command run inside this directory where to find its true database.
graph LR
WT["📁 Child Working Tree\n(hotfix-branch)"] -->|".git file"| M["📂 Main Repo metadata\n(.git/worktrees/hotfix-branch)"]
style WT fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style M fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
Layer 2 — Isolated Metadata in .git/worktrees/
Every working tree must have its own staging area (the index file), active branch (HEAD), and temporary workflow state (such as BISECT_LOG or rebase state) to prevent direct conflicts with the main working directory.
To solve this, Git automatically creates a subdirectory inside the main repository's .git folder at .git/worktrees/<name>/.
.git/
└── worktrees/
└── hotfix-branch/
├── HEAD # Points to the local 'hotfix' branch
├── index # The worktree's isolated staging area
├── gitdir # Points back to the child worktree's .git file
└── commondir # Points to shared folders in the main repo
When running git status or git add in the sub-directory, Git reads and writes to the index and HEAD files located under .git/worktrees/hotfix-branch/. This isolates the staging area of each working tree. You can stage and commit files in the child directory without affecting the staging area of your main directory.
graph TD
WT_Main["📁 Main Working Tree"] -->|uses| M_HEAD["Main HEAD"]
WT_Child["📁 Child Working Tree"] -->|uses| C_HEAD["Child HEAD\n(.git/worktrees/child/HEAD)"]
style WT_Main fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style WT_Child fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style M_HEAD fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
style C_HEAD fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
Layer 3 — Shared Database via Commondir
Commit history (objects) and branch references (refs) represent the shared state of the repository. If each worktree kept its own copy of these databases, spinning up a worktree would be just as slow and storage-intensive as cloning a fresh copy of the repository.
Git handles this shared access using the commondir file located inside each worktree's metadata folder. This file contains a single path:
../..
This path instructs Git to route requests for shared folders like objects/, refs/, info/, hooks/, and the repository config file back to the root .git folder (two levels up from .git/worktrees/hotfix-branch/).
Thanks to this routing mechanism, any commit created in a worktree is immediately written to the main repository's database. When returning to the main working tree, that commit is already available without requiring any sync steps. To ensure history integrity, Git also prevents checking out the same branch on two active working trees simultaneously.
graph LR
subgraph "Main Repo (.git/)"
CommonDB["🗃️ Common Database\n(objects, refs)"]
end
subgraph "Worktree child metadata"
C_Meta["📁 .git/worktrees/child/"] -->|commondir| CommonDB
end
style CommonDB fill:#1a3a2a,stroke:#22c55e,color:#86efac
style C_Meta fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
Full picture
Here is a high-level view showing how Git coordinates these parts to keep multiple working trees running in parallel on a single database:
graph TD
subgraph "Working Trees (Source Directories)"
MainWT["📁 Main Project Folder\n(/projects/my-app/)"]
ChildWT["📁 Child Worktree Folder\n(/projects/my-app-hotfix/)"]
end
subgraph "Main Repository (.git/)"
MainMeta["📂 Main .git/ Folder"]
CommonDB["🗃️ Shared Database\n(objects/, refs/, config)"]
subgraph "Worktrees Directory"
ChildMeta["📂 .git/worktrees/my-app-hotfix/"]
ChildHEAD["Local HEAD"]
ChildIndex["Local index"]
end
end
MainWT -->|contains| MainMeta
ChildWT -->|contains flat .git file| ChildMeta
ChildMeta -->|commondir| CommonDB
MainMeta -->|contains| CommonDB
ChildMeta -->|contains| ChildHEAD
ChildMeta -->|contains| ChildIndex
style MainWT fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style ChildWT fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
style MainMeta fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
style ChildMeta fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
style CommonDB fill:#1a3a2a,stroke:#22c55e,color:#86efac
Takeaway
At its core, git worktree is not magic, but a clean exercise in data decoupling. By splitting runtime working states (HEAD, index) into local trees and sharing the persistent storage (objects, refs) via commondir, Git provides high-efficiency multitasking.
Understanding this design gives you the confidence to clean up broken worktrees (simply delete the folder and run git worktree prune) or manually repair paths in .git/worktrees if you ever move your project directories. A solid grasp of database design patterns always brings peace of mind when debugging complex setups.
Related Posts
Zoom-in: Git Commit
git commit -m 'fix bug' — one command. Under the hood is an immutable, content-addressed data structure that explains why rebase, cherry-pick, and merge work the way they do.
7 Git Aliases That Save 1 Hour Every Day
You type `git status`, then `git add .`, then `git commit` every time? There's a faster way. Here are 7 git aliases and configs used daily to save hours each week.
Zoom-in: Rate Limiter
You send too many API requests, and the system responds with '429 Too Many Requests'. How does the Rate Limiter gatekeeper protect system resources?