Every agent I dispatch gets its own throwaway copy of the repository. It branches there, commits there, and when it’s done I take the pull request and the copy gets deleted. The whole arrangement rests on one assumption: the agent is actually in the throwaway copy and not in my working checkout — the one with my uncommitted changes in it.
So the first instruction every agent gets is: prove it before you touch anything.
The interesting part is which command proves it, because the one that feels right is wrong.
The mechanism, briefly
Git has a feature called a worktree: a second working directory attached to the same repository. You get a separate folder with its own checked-out branch, but it shares one underlying object store with the original. Think of a shared warehouse with several loading docks — different docks, same inventory.
That sharing is the point. It’s also the trap.
If you ask a worktree where its git data lives, you get a path that points back into the original checkout:
$ git rev-parse --git-dir
/opt/projects/my-repo/.git/worktrees/task-3
$ git rev-parse --git-common-dir
/opt/projects/my-repo/.git
Both of those answers name the primary checkout. Both are correct. Neither one tells you whether you are standing in it.
I want to sit on that for a second, because this is the actual lesson and the git specifics are just the vehicle. --git-dir answers “where is my metadata.” The question I care about is “where am I.” Those produce different answers, and the first one is more authoritative-sounding while being completely useless for the check I’m making. An agent that runs --git-dir, sees a path containing worktrees/, and concludes “I’m isolated” has reasoned from a real fact to a correct-looking conclusion by way of a coincidence.
Worse, the failure is asymmetric. In the safe case it happens to give a comforting answer. In the dangerous case — an agent that got launched in the primary checkout by mistake — --git-dir returns /opt/projects/my-repo/.git, which is also a perfectly normal-looking answer. There’s no shape you can pattern-match on that separates the two reliably.
The check that actually answers the question
pwd -P
git rev-parse --show-toplevel
--show-toplevel gives the root of the working tree you are standing in, not the repository the working tree belongs to. pwd -P resolves symlinks so a symlinked path can’t disguise where you really are. If either resolves to the primary checkout, the agent stops — it does not branch, does not commit, and reports that it was launched in the wrong place.
That’s it. Two commands, and the rule that goes with them: the path is authoritative; the git plumbing is diagnostic. The plumbing commands are still useful for understanding the repo’s structure. They just don’t get a vote on this particular question.
The general version
Strip out git and the pattern is one I keep re-encountering: verifying a proxy for the property instead of the property.
- Checking that a process is running, when what you need to know is whether it can reach its target. I wrote about a rescue daemon that reported healthy while blind — same shape.
- Checking that a pull request merged, when what you need is whether the new code is running. That’s merged is not running.
- Checking that tests pass against mocks, when what you need is behavior against the real system. Green on mocks is not done.
Each one has the same tell, and it’s a good tell because you can check for it deliberately: the proxy and the property agree in every case you thought to try, and disagree exactly in the case you’re afraid of. That’s not a coincidence. It’s why you reached for the proxy — it was correlated in all the situations you had in mind. The situation you didn’t have in mind is the one the check exists for.
The question I now ask when writing any guard: what’s the case where this check passes and the thing I care about is false? If I can’t construct that case, I don’t understand the check well enough to rely on it. If I can construct it and it’s the exact scenario the guard was written for, I have a proxy problem.
Why the stop is absolute
One more design decision worth naming: when the path check fails, the agent stops completely. It doesn’t try to fix its situation, doesn’t create a worktree for itself, doesn’t move to a safer directory.
That’s deliberate, and it cost me an argument with myself. Self-correction is usually the better behavior — an agent that resolves its own problem is worth more than one that files a ticket. But this specific failure means the agent’s understanding of its environment is already wrong, and an agent acting confidently on a wrong model of where it is happens to be the exact thing the guard exists to prevent. Recovery logic written by a component that has just proven it’s confused is not recovery logic I want running against my working checkout.
So it reports and halts. The same instinct as not running git checkout inside a loop’s working directory: when the blast radius includes uncommitted human work, the correct amount of cleverness is none.
A guard that can act is a guard that can be wrong in a new way. Guards should stop.