PricingSign inDownload

Blog / git

Clean up git worktrees without breaking a running agent

git worktree remove deletes a clean worktree while an agent still works in it. What the agent sees next, a check for running processes, and git worktree lock.

On this page
  1. git removes a worktree with a process inside it
  2. What the agent sees next
  3. Check for processes before you remove
  4. Lock the worktrees agents are using
  5. If it already happened
  6. The short version
  7. Try it with your team

Running agents in parallel means worktrees: one folder per branch, so two agents never edit the same checkout. After a few days you have a dozen of them, and cleaning up is a loop of git worktree remove. The catch is that git has no idea an agent is still working in one of them.

We tested what happens with git 2.54 on macOS: a process sitting in a worktree, the worktree removed under it, and what that process sees next.

git removes a worktree with a process inside it#

We made a worktree, started a process whose working directory was that worktree, and removed it:

bash
git worktree add ../feat -b feat
(cd ../feat && sleep 30) &
git worktree remove ../feat

git worktree remove refuses only when the worktree has changes:

fatal: '../feat' contains modified or untracked files, use --force to delete it

With a clean worktree it succeeded at once, with the process still inside. Most worktrees look clean between an agent's edits, so "git refused, so it must be in use" is not a check you can rely on.

What the agent sees next#

We ran a script inside a worktree, removed the worktree two seconds in, and let the script carry on:

Command inside the removed folderResult
pwdStill prints the old path
git statusfatal: Unable to read current working directory: No such file or directory
echo hi > out.txtNo such file or directory
node -e 'process.cwd()'Error: ENOENT: process.cwd failed … the current working directory was likely removed
npm -vFails with the same Node error

pwd keeps answering because the shell remembers the path, which is what makes this confusing: the agent thinks it's in a folder that is gone, and every git, node or npm command it tries fails. An agent in this state tends to retry, or report errors that look unrelated, until someone restarts it in a real folder.

The branch survives. git branch --list feat still showed it after the removal, so committed work isn't lost. Uncommitted work is: --force deletes the changes along with the folder.

Check for processes before you remove#

lsof can list every process whose working directory is in a folder:

bash
lsof -d cwd | grep "$(cd ../feat && pwd -P)"

On our worktree it printed:

sleep     15835 mahmoud  cwd    DIR   1,17      128 1057634354 /private/tmp/b3-wt/feat3

Note the path: on macOS /tmp is really /private/tmp, and lsof prints the real path. pwd -P gives you that real path, so the grep matches. On Linux, lsof works the same way.

A cleanup loop that skips anything in use:

bash
git worktree list --porcelain | sed -n 's/^worktree //p' | tail -n +2 | while read -r wt; do
  real=$(cd "$wt" 2>/dev/null && pwd -P) || continue
  if lsof -d cwd 2>/dev/null | grep -qE "$real(/|$)"; then
    echo "skip (in use): $wt"
  else
    git worktree remove "$wt" || echo "kept (has changes): $wt"
  fi
done

tail -n +2 skips the first entry, which is always your main checkout. The (/|$) stops ../feat from matching a process in ../feat2. The loop never passes --force, so a worktree with changes stays and says why. An agent that's waiting for you, with no command running, is still a process with its working directory there, so it shows up too.

Lock the worktrees agents are using#

git has a lock for exactly this:

bash
git worktree lock --reason "agent running" ../feat

With the lock in place:

$ git worktree remove ../feat
fatal: cannot remove a locked working tree, lock reason: agent running
use 'remove -f -f' to override or unlock first

A single --force doesn't get past it either; only remove -f -f or git worktree unlock ../feat does. git worktree prune also leaves locked worktrees alone, and git worktree list --porcelain shows a locked agent running line, so anyone looking can see why it's there.

The habit that works: lock a worktree when you start an agent in it, unlock it when the branch is merged, then remove it.

bash
git worktree add ../feat -b feat && git worktree lock --reason "agent running" ../feat
# ...agent works, branch merges...
git worktree unlock ../feat && git worktree remove ../feat

Claude Code can create a worktree for you with claude --worktree <name>. Whoever creates it, the same rule holds: git only protects it from removal if it's locked or has uncommitted changes.

If it already happened#

The worktree is gone and an agent is stuck in it. Committed work is on the branch, so put the folder back from it:

bash
git worktree add ../feat feat

We committed a file in a worktree, removed the worktree, and ran that line. Git answered HEAD is now at ef1e556 agent work, and the file was back. Then restart the agent in the new folder, or tell it to cd there. An agent that only lost its folder picks up from the last commit; anything it hadn't committed is gone and has to be redone.

If someone deleted the folder with rm -r instead of git worktree remove, git still thinks the worktree exists. git worktree list marks it prunable, and adding it again fails:

fatal: '/tmp/b3-wt/r2' is a missing but already registered worktree;
use 'add -f' to override, or 'prune' or 'remove' to clear

git worktree prune clears the stale entry (-v prints gitdir file points to non-existent location for each one it removes), and then git worktree add works again. Locked worktrees are skipped by prune, which is one more reason to lock the ones in use.

The short version#

  • git worktree remove deletes a clean worktree even while a process is working inside it.
  • The agent left behind still sees its old path in pwd, but git, node and npm fail.
  • Check first with lsof -d cwd | grep "$(cd <worktree> && pwd -P)".
  • git worktree lock --reason "…" blocks remove, remove -f and prune until you unlock it.
  • Never script cleanup with --force: it deletes uncommitted work.

On a team, the worktree an agent is using may belong to a teammate's session on a shared machine, and removing it stops their work, not yours. A locked worktree with a reason is the note that tells them. For setting new worktrees up so the agent can start working at once, see git worktrees, .env and node_modules.