Using Git Worktrees: Isolated Workspaces That Never Touch Main

Introduction: The Problem Branches Don’t Solve

Git branches isolate history. They do very little to isolate your working directory. Every branch still shares one checkout, one node_modules, one virtualenv, one set of build artifacts — and one pile of half-finished edits. The scenario is painfully familiar: you are four hours into a feature, thirty files deep in changes that are not committable yet, and a production bug report lands that needs a fix on main right now. Every option with a single working tree is the same bad move in a different hat: stash and hope, commit an embarrassing work-in-progress, or clone the whole repository again. All of them disturb the state you had carefully built.

The Superpowers using-git-worktrees skill answers with git’s own native mechanism: a second working directory, checked out to a different branch, sharing the same repository. A worktree. Its core principle is one line: systematic directory selection plus safety verification equals reliable isolation — because isolation you assume is not isolation you can trust.

What the Using Git Worktrees Skill Actually Does

At its core, this skill creates an isolated workspace for feature work without touching the workspace you are standing in. It does it in five deliberate moves:

  1. Choose the worktree directory by priority, never by guess. First check what already exists (ls -d .worktrees worktrees) — if a convention is already in the repo, follow it, with .worktrees winning if both exist. If nothing exists, look for a preference in .hermes.md or project config. If there is still nothing, ask the user where worktrees should live. The skill refuses to invent a location silently.
  2. Verify the directory is gitignored. A project-local worktree directory that is not ignored is a bug waiting to be committed. The skill checks with git check-ignore -q .worktrees and, if the check fails, adds the entry to .gitignore and commits that fix before anything else happens.
  3. Create the worktree with its own branch. git worktree add .worktrees/$BRANCH_NAME -b $BRANCH_NAME produces a full checkout in one command — no second clone, no remote juggling, because the object database and history are shared with the main repo.
  4. Run project setup inside the new workspace. Auto-detect and execute: pip install -r requirements.txt or an editable pyproject.toml install, npm install, cargo build, go mod download. A worktree that shares nothing with main includes sharing nothing in its installed dependencies.
  5. Verify a clean baseline, then report. Run the test suite before any feature work begins. If tests fail at baseline, the skill reports the failures and asks whether to proceed or investigate — it never builds a feature on a broken foundation. Success ends with a report: full path, tests passing with a count, ready to implement.

The Safety Verification: Independence Is Proven, Not Assumed

The claim that matters is that each worktree is independent: work inside it cannot affect the main checkout. That is largely true by construction — a worktree is a separate directory with its own checked-out files, dependencies, and processes. Editing, breaking, or deleting a worktree changes nothing about main’s working state; a failed experiment ends with one removal command, and main never knew it existed.

The skill hardens that construction with three checks, and each one exists because someone got bitten:

One honest caveat keeps the independence claim precise: worktrees share the repository’s object database and refs. Branch names, commits, and tags are shared state — git will refuse to check out the same branch in two worktrees at once, and deleting a branch affects everyone. Worktrees isolate working directories, not history. The skill’s model works because each worktree gets its own branch by construction.

Anatomy of a Worktree Creation, Step by Step

Concretely, a skill-driven creation looks like this:

# 1. Verify the directory is ignored (silent = ignored)
git check-ignore -q .worktrees 2>/dev/null || echo "add to .gitignore + commit first"

# 2. Create the worktree with a fresh branch
project=$(basename "$(git rev-parse --show-toplevel)")
git worktree add .worktrees/$BRANCH_NAME -b $BRANCH_NAME
cd .worktrees/$BRANCH_NAME

# 3. Project setup (auto-detected)
[ -f requirements.txt ] && pip install -r requirements.txt
[ -f package.json ] && npm install
[ -f Cargo.toml ] && cargo build
[ -f go.mod ] && go mod download

# 4. Clean baseline
pytest tests/ -q

followed by a report in a fixed shape: worktree ready at full path, tests passing with a count, ready to implement feature name. The quick-reference table the skill carries doubles as a list of its failure modes:

SituationAction
.worktrees/ or worktrees/ already existsUse it (verify it is ignored)
Neither existsCheck project config → ask the user
Directory not gitignoredAdd to .gitignore and commit first
Tests fail at baselineReport failures, ask before proceeding

Real Example: Testing a New Superpowers Install While Keeping the Old One Alive

The clearest demonstration of why this matters is the Superpowers plugin updating itself. Suppose the old plugin state — the skill set the agent is actively running on — must stay fully functional while a new version is tested. The naive path mutates the live install in place: if the new version is broken, the agent is reasoning with skills that no longer load, and the only reference point was the state just destroyed.

The worktree path keeps both states alive simultaneously. A worktree off the plugin repository gives the candidate version its own checkout at .worktrees/plugin-upgrade; the new skills get installed, loaded, and exercised there while the old plugin continues to serve every session from the main checkout, untouched. The evaluation is genuinely comparative — old and new behavior exist in the same moment, over the same tasks. If the new version holds up, it merges like any branch. If it doesn’t, git worktree remove .worktrees/plugin-upgrade deletes the experiment and the old state was never at risk. Rollback stops being an operation you perform and becomes a thing that was simply never needed.

When to Use a Worktree

SituationWhy a worktree wins
Feature branches that need isolation from current workThe in-progress workspace stays exactly as it is; the feature gets its own directory
Executing an implementation plan in a separate branchThe plan’s commits, setup, and test runs never touch the main checkout — a clean failure is a deletable failure
Parallel experimentsTwo candidate solutions to the same problem, built and benchmarked side by side on one repo, no stash ping-pong
Hotfixes while main work is mid-editThe urgent fix happens in its own tree; thirty files of uncommitted feature work never move

Two situations do not call for a worktree. Minor edits on a clean tree: just check out a branch — the ceremony outweighs the benefit. And subagent isolation: delegated subagents in Hermes already get task-scoped file-operation isolation, so a worktree per subagent buys nothing. Worktrees are for when you need to work a branch without disturbing the workspace you are standing in.

The Handoff: finishing-a-development-branch

A worktree is a borrowed workspace, and borrowed workspaces get returned. That is the job of the companion skill finishing-a-development-branch, which closes out the work the worktree was created for: merging or opening the pull request, removing the worktree with git worktree remove <path>, and cleaning up the branch. The most common mistake in the skill’s own table is the quiet one — forgetting cleanup. A repository with six lingering worktrees and their zombie branches is a repo whose next agent inherits the exact coupling this skill was built to prevent. The full loop is: worktree to isolate the work, finishing-a-development-branch to integrate it and erase the scaffolding.

Related Deep Dives in the Series

Frequently Asked Questions

Q: Is a worktree the same as a second clone?
No — and it is better for most cases. A worktree shares the main repository’s object database and refs, so it is created in a second, costs no network transfer, and stays in sync automatically. What it cannot share is a checked-out branch: the same branch cannot be live in two worktrees at once.

Q: Why does the baseline matter so much if the worktree is isolated?
Because isolation guarantees the worktree is not affected by main’s uncommitted mess, not that the branch it checked out is green. Tests can fail at creation because the base commit itself is broken. Finding that out before implementing — instead of after three hours — is the difference between attributing failures and untangling them.

Q: What happens to the worktree’s commits if I delete the directory?
The commits live in the shared repository, so branch history survives — but git keeps protective state, and the supported path is git worktree remove as part of finishing a development branch, which cleans the checkout and the bookkeeping together.

Key Takeaways

Next in this series: finishing a development branch — what to do when the isolated work is done and needs to come back. Browse the full Skills We Use series index for every article.