Managing a Dozen Worktrees Without Knowing Git Well
Let me admit something first: git is my weak spot.
Multiple branches, remotes, rebase, merge, conflicts, reading the commit graph, force-push — none of it comes naturally. I’ve caused real accidents, too: a tangled history, and days of changes that had to be recovered one by one.
Yet today, one machine has a dozen-plus worktrees open, several agents working in parallel on different branches, and several PRs opened a day. By rights this is exactly where git goes wrong most easily. In practice, things are clearer than when I did everything by hand.
The trick wasn’t mastering git first. It was letting AI handle git, while I own a few disciplines it must follow and a few questions I can ask at any time.
1. The four questions I ask
I don’t read the commit graph, but I do ask questions. The four I use most:
“List every worktree and branch for this repo on this machine: who created it, when, how far behind main it is, and whether it exists on the remote.” This one sentence lays the whole situation out. Which are mine and which are someone else’s, which are merged, which are far behind, which exist only locally and were never pushed — all visible in one table.
“Delete the worktrees and branches I created that are already merged.” Cleanup is the easiest thing to skip. Branches that aren’t deleted become, a few weeks later, dozens of pointers whose owner and purpose nobody knows. I now say this after every merge.
“Is this PR merged? Did the commit I just pushed make it into main?” This one was learned the hard way. I casually merged a PR on the web page; the agent didn’t know, pushed two more commits to the same branch, and told me “PR updated” — when in fact those two commits never reached main. The rule now: before adding commits to an existing PR, check its state; if it’s already merged, cut a new branch from the current position and open a new PR.
“Who created this?” When I see an unfamiliar branch or a commit I didn’t make, I ask about ownership first. Other people’s work is not deleted, not modified, and not merged in passing.
2. The disciplines the AI must follow
These rules live in memory synced across machines, and every agent reads them before doing git operations. Each one maps to a real accident.
A branch name should show who created it and on which machine. My convention is <machine>/<agent>/<description>. With several machines and several agents working at once, the name alone tells you who owns it — no need to dig through logs.
In a shared directory, “switching branches” is not a local operation. Uncommitted changes follow HEAD. Once, an agent switched branches in a shared directory and carried 41 files another session was actively editing onto its own branch. So every task gets its own worktree; nobody switches back and forth in a shared directory.
No bare git stash. The stash stack is shared by the whole repository and belongs to no branch. On a clean working tree, stash saves nothing, and the following pop pops whatever another parallel session stashed. To temporarily revert a file, use git checkout <ref> -- <path>; to compare against a baseline, open a temporary worktree.
Pipes swallow exit codes. git push | tail returns tail’s exit code. A failed push and a hook rejection look exactly like success. To see full output, write it to a file and read it, or turn on pipefail.
After a rebase, use --force-with-lease; don’t pull again. A pull merges the old remote history back in, and that’s how history gets tangled. And force never touches the main branch.
Merging is a human action. Agents can push their own branches and open draft PRs; they can’t merge, can’t push to main, and can’t change branch protection. My private memory repos are the exception: there I let agents fast-forward-merge on their own, but a real conflict means they must stop and report.
3. Too much parallelism is a net loss
I’ve overdone it, too: seven worktrees and three automated loops running at once. The result was dirty code whose ownership could only be untangled by diffing each one, and unpushed commits climbing from a hundred-something to over four hundred.
The root cause wasn’t laziness; it was conservation of review budget. AI pushed the cost of “writing” close to zero, but “verify, collect, push, merge, ship” got no cheaper, and it all piled up at the human. So now I set a convergence line on parallelism and close things out regularly: after each batch, merge and delete first, then start the next.
4. After a long task is killed, clean up the scene first
Some problems aren’t git itself, but what’s left around it. When a long task is killed midway, common leftovers include:
- a temporarily quarantined config file that was never restored, so the next rebase is refused for “unstaged changes”;
- tests stopped halfway, leaving an orphaned server still holding a port;
- lock files left behind by force-killed scripts;
- a reclaimed shell, but a dev server started from the worktree still alive — stop it before deleting the worktree.
And one judgment call: killing a background wrapper process doesn’t mean the serial script inside it stopped. Judge progress by git log, not by the process list.
5. The git I’ve learned is just enough
After a few months of this, my understanding of git is actually better than before — not from tutorials, but because every time something went wrong, the AI explained it with a few simple mental models:
- A branch is just a pointer; deleting a branch doesn’t delete commits.
- Rebase changes commit hashes, so commits others already have must not be rebased.
- To know whether something was merged, check whether the commit is an ancestor of main (
git merge-base --is-ancestor).
Those three are enough for me to tell whether what an agent says is true.
Closing
You don’t always have to fix a weakness before moving forward. I’m still not fluent in git, but I know what to ask, what to forbid, and where to look when something breaks.
I ask the questions, the AI types the commands, the disciplines become rules, and the merge button stays in my hand. For someone who isn’t fluent in git, that is far more reliable than memorizing every command.