Git Worktrees skill for Codex
Two branches checked out at once, no stashing. Codex creates a worktree per concurrent task, inside the repo when the sandbox only allows writes there, and gets each one runnable with its own dependencies, port and Docker project name. It knows the stash, branches and config are shared while node_modules and .env are not, and it cleans up by checking every worktree for uncommitted or unpushed work before removing anything.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- git-worktrees Installs to ~/.agents/skills/git-worktrees/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install git-worktrees The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project git-worktrees Installs to .agents/skills/git-worktrees/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/git-worktrees.zipunzip -o git-worktrees.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) git-worktrees PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $git-worktrees, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Runs several branches side by side with git worktree - add, list, lock, move, remove and prune - for reviewing a PR without stashing, hotfixing a release while a feature is half done, comparing old and new behaviour, or running long test suites in parallel. Covers what worktrees share (objects, refs, stash, config) versus keep separate (index, untracked files, node_modules, .env), per-worktree setup, port and Docker collisions, sandbox-friendly locations and safe cleanup. Use when asked to work on two branches at once, set up worktrees, or clean up stale ones.
Use it when
- You need a second branch running while your current work stays untouched
- Reviewing or testing a PR locally mid-task
- Comparing behaviour between main and a branch side by side
- Stale worktrees or 'already checked out' errors need sorting out
Use something else when
- You want several Codex agents working in parallel on one repo: use codex-parallel-worktrees
- You are managing a chain of dependent PR branches: use stacked-pull-requests
How to check the result
git worktree list shows each task on its own branch or detached commit, each worktree's app or tests run on distinct ports, and cleanup removed only worktrees that were clean and pushed.
The mistake it prevents
Deleting a worktree folder with rm -rf, which strands its metadata and silently discards any uncommitted changes inside it.
Worktrees are used well when each concurrent task has its own directory and branch, a new worktree is runnable within a minute through a repeatable setup step, nothing collides (ports, Docker project names, databases), and cleanup never deletes uncommitted or unpushed work without the user saying so.
When to reach for one
| Situation | Worktree? |
|---|---|
| Review or run a PR while your branch has uncommitted work | Yes, detached at the PR head |
| Urgent hotfix on a release branch mid-feature | Yes, new branch from the release branch |
| Compare behaviour between main and your branch side by side | Yes, detached at origin/main |
| Long test or build running while you keep coding | Yes |
| Several agent tasks in parallel on one repo | Yes, one worktree per task |
| Quick look at an old version of one file | No: git show origin/main:path/to/file |
| Switching branches with a clean tree and nothing running | No: git switch is enough |
Before creating
git worktree list
git status --short # nothing here moves into the new worktree
git fetch origin # needs network
Read AGENTS.md for install, env and dev-server commands; the setup step reuses them.
Where to put them
| Layout | Path | Notes |
|---|---|---|
| Inside the repo, ignored | .worktrees/<name> | Works inside a sandbox whose writable root is the repo. Add .worktrees/ to .git/info/exclude so it never shows as untracked |
| Sibling directory | ../<repo>-<name> | Clean separation; may be outside the writable area of a Codex workspace-write sandbox, so ask for approval or use the in-repo layout |
| Bare-repo hub | repo/.bare plus repo/main, repo/feature-x | Good for people who live in worktrees; restructure only with the user’s agreement |
Tools that search for files recursively (test runners, linters, IDE indexers) may descend
into .worktrees/. Exclude it in their configs if they do.
Creating
# New branch from the remote default branch
git worktree add -b fix/login-timeout .worktrees/login-timeout origin/main
# Existing local branch (must not be checked out anywhere else)
git worktree add .worktrees/release-2-4 release/2.4
# Read-only look at a commit or PR head, no branch
git worktree add --detach .worktrees/main-baseline origin/main
git fetch origin pull/123/head:pr-123 && git worktree add .worktrees/pr-123 pr-123
A branch can be checked out in only one worktree. The error
'<branch>' is already checked out at '<path>' means use that worktree, use --detach,
or create a new branch. Do not reach for --force to check it out twice.
Setting up a worktree to run
Untracked files do not come along. Each worktree needs its own:
cd .worktrees/login-timeout
pnpm install --frozen-lockfile # pnpm and uv share a global store, so this is fast
cp ../../.env.example .env # copy real secrets from the main checkout only if the user agrees
export PORT=3101 # avoid clashing with the main checkout's dev server
export COMPOSE_PROJECT_NAME=app_login_timeout # separate Docker containers and volumes
If the repo needs more than a couple of these steps, propose a script
(scripts/worktree-setup.sh) so every worktree is set up the same way. Shared resources
such as one local Postgres database still collide; use a separate database name per
worktree when tests write data.
What is shared and what is not
| Shared across all worktrees | Separate per worktree |
|---|---|
| Object database, branches, tags, remotes | HEAD, index (staging area), working files |
git stash list | Untracked and ignored files: node_modules, .venv, .env, build output |
Repository config and hooks (unless extensions.worktreeConfig plus git config --worktree) | Bisect state, in-progress rebase or merge |
rerere cache | refs/worktree/* |
Consequences: a commit made in one worktree is immediately visible as a branch in the
others; git stash pop in the wrong worktree applies another task’s changes; a
git rebase in progress blocks only its own worktree.
Housekeeping
git worktree list --porcelain
git worktree lock --reason "on external drive" .worktrees/big-data # protect from prune
git worktree move .worktrees/pr-123 .worktrees/pr-123-review
git worktree repair # after moving the main repo or a worktree by hand
Safe cleanup
Never delete a worktree directory with rm -rf; it strands metadata and can lose work.
Check each candidate first:
for wt in $(git worktree list --porcelain | awk '/^worktree /{print $2}' | tail -n +2); do
dirty=$(git -C "$wt" status --porcelain | wc -l | tr -d ' ')
branch=$(git -C "$wt" rev-parse --abbrev-ref HEAD)
if git -C "$wt" rev-parse -q --verify '@{u}' >/dev/null 2>&1; then
unpushed=$(git -C "$wt" rev-list --count '@{u}..HEAD')
else
unpushed="no-upstream"
fi
echo "$wt branch=$branch dirty=$dirty unpushed=$unpushed"
done
- Clean, pushed or merged:
git worktree remove <path>. - Dirty, unpushed, or no upstream: show the list to the user and ask.
git worktree remove --forcediscards uncommitted changes and needs explicit approval. - Directory already gone:
git worktree pruneremoves the stale entry. - Delete the branch separately, and only if merged:
git branch -d <branch>(lower-case-drefuses unmerged branches;-Dneeds approval).
Gotchas
- Submodules are not initialised in new worktrees; run
git submodule update --init --recursiveinside each. - Editors opened on the parent directory index every worktree; open the worktree folder itself.
- Some older tools expect
.gitto be a directory; in a worktree it is a file pointing to.git/worktrees/<name>. Usegit rev-parse --git-dirin scripts. - Absolute paths in generated config (virtualenv shebangs, caches) point at the worktree
that created them; do not copy
.venvornode_modulesbetween worktrees. - Running two dev servers or test suites against the same port, Docker project or database gives misleading failures; isolate them as above.
Output
Worktrees:
- .worktrees/login-timeout branch fix/login-timeout (from origin/main), deps installed, PORT=3101
- .worktrees/pr-123 detached at PR #123 head, tests running
Cleanup proposed: .worktrees/old-spike (clean, merged) -> remove; .worktrees/exp (3 uncommitted files) -> kept, needs your call What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Git Worktrees"
short_description: "Two branches checked out at once, no stashing"
default_prompt: "$git-worktrees I'm mid-feature. Check out PR 123 in a worktree so I can run it without stashing" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.