While I was building RateMyAzureFirewall I had Copilot CLI working through the secured hub flow on a feature branch, the one that resolves Firewall Policy inheritance before it rates anything. About twenty minutes in I spotted that the grade caps on the VNet flow were wrong on main. Threat intelligence set to Alert was supposed to cap the grade at C and it wasn’t. Easy fix. Except the agent was mid-change, my working directory was full of half-finished files, and I did what I always do: stashed everything, switched to main, fixed the cap, switched back, popped the stash, and then spent ten minutes working out why the build was now broken. It wasn’t the agent’s fault. I’d interrupted it, moved the floor, and put it back slightly differently.
That is the exact problem git worktrees solve, and they’ve solved it since 2015. Nobody cared much until recently. Now they’re everywhere, and the reason is coding agents.
What a worktree actually is#
A normal git clone gives you one working directory and one checked-out branch. If you want to look at a different branch, you switch, and the files on disk change underneath you. One repo, one branch at a time.
A worktree is a second working directory attached to the same repository. Same history, same remotes, same objects on disk, but a different folder with a different branch checked out. You can have as many as you like. Each one is a normal folder you can open in VS Code, run dotnet build in, or point an agent at, and none of them know or care what the others are doing.
The old way to get this was to clone the repo twice. That works, but the clones don’t share anything, you fetch twice, and you end up pushing from one and forgetting the other exists. Worktrees share the .git directory, so a fetch in one is a fetch in all of them, and a branch you create in one is visible to the rest straight away.
The four commands you need#
There are more, but these are the ones I use. Start in your existing clone.
To create a new branch and a worktree for it in one go, in a sibling folder:
git worktree add -b feature/secured-hub ../ratemyazurefirewall-secured-hubYou’ll see Preparing worktree (new branch 'feature/secured-hub') and a new folder appears next to your clone with that branch checked out. If the branch already exists, drop the -b and put the branch name last:
git worktree add ../ratemyazurefirewall-logs feature/log-viewTo see what you’ve got:
git worktree listThat prints one line per worktree with the path, the commit and the branch in square brackets. The first line is always your original clone.
When you’re done with one:
git worktree remove ../ratemyazurefirewall-secured-hubThat deletes the folder and unregisters it. The branch stays, so you can still merge or push it. If you delete a worktree folder by hand instead, git keeps a stale record of it until you run git worktree prune.
One gotcha that caught me the first time. You can’t check out the same branch in two worktrees. If you try to add a worktree for main while your original clone already has main checked out, you get this:
fatal: 'main' is already used by worktree at 'C:/repos/ratemyazurefirewall'Git is right to stop you. Two folders both committing to the same branch would be a mess. Make a branch per worktree and the problem goes away.
Why they’re back in fashion#
For most of the last decade the honest answer to “why don’t you use worktrees?” was “stash works fine”. One person, one task at a time, the occasional hotfix in between. Switching branches costs a few seconds and a rebuild. Worktrees were a nice trick that nobody needed.
Coding agents changed the arithmetic. If you’ve read my posts on building your first custom agent with Copilot CLI or shipping features faster with it, you’ll know the pattern: you describe a change, the agent goes off and edits files for a few minutes, you review. The bit that’s new is that “a few minutes” is long enough for you to want to do something else. Two agents on two tasks. One agent working while you review the last thing it did. An agent on a long refactor while you fix a typo on main.
All of those need more than one working directory, because an agent editing files in the folder you’re also editing is exactly the stash-and-switch mess from the top of this post, only faster and with less warning. Worktrees give every agent its own floor to stand on.
The tooling has caught up with this. Claude Code has a --worktree flag that creates one for you under .claude/worktrees/ on a fresh branch, runs the session there, and cleans up afterwards if nothing was left behind, according to its worktrees documentation. Copilot CLI doesn’t have a built-in equivalent as far as I can find, but it doesn’t need one. It uses whatever git state is in the folder you start it from, so you create the worktree yourself and run copilot inside it. VS Code added worktree support to the Source Control view in mid-2025 as well, so the folders show up as separate repositories in the sidebar rather than looking like unrelated clones. I’m going on the release announcements for that rather than having studied every version, so check the release notes for the exact build if it matters to you.
What I do now with the RateMy apps#
Here’s the setup I’ve settled on for the RateMy family, since it’s the same shape every time. One original clone on main that I never do feature work in. Every task gets a worktree.
When I started the firewall app’s log classification work, the one that splits activity into Likely real attack, Possible false positive and Needs review, I did this from the clone:
git worktree add -b feature/log-classification ../ratemyazurefirewall-logs
cd ../ratemyazurefirewall-logs
copilotThen I asked Copilot CLI to build the classification logic and walked away. Meanwhile, in a second terminal:
git worktree add -b fix/alert-cap ../ratemyazurefirewall-alert-cap
cd ../ratemyazurefirewall-alert-capI fixed the grade cap by hand, ran the tests, committed, pushed, raised the PR and merged it. Total elapsed time about eight minutes. The agent in the other folder never noticed. When it finished, I rebased its branch onto main, reviewed the diff, and pushed that too. No stash, no “why is the build broken”, no half-applied agent edits sitting in the wrong branch.
Two practical things I learned doing this. First, the bin and obj folders are per-worktree, so the first dotnet build in a new one is a cold build. That’s a minute or so on these apps and not worth worrying about. Second, anything in .gitignore is not shared. For the RateMy apps that means appsettings.Development.json and any local .env has to be copied into each new worktree, or the app starts with no configuration and you spend a confused minute wondering why the subscription picker is empty. I keep a tiny script that creates the worktree and copies those files across in one go, which is less clever than it sounds.
The same trick applies to this blog. The Hugo site lives in one repo, and more than once I’ve had a post half-drafted on a branch when I’ve wanted to fix a broken link in a published one. A worktree for the fix means the draft is never touched.
Caveats#
Worktrees aren’t free of sharp edges. The branch-per-worktree rule above is the one people hit first. The second is disk usage: the git objects are shared, but the working files, build outputs and node_modules or NuGet restores are not, so a big repo with five worktrees is five times the checkout on disk.
Submodules are awkward in worktrees, and if your repo relies on them, test before committing to this workflow. Some tools also assume a .git folder exists in the working directory, when in a worktree it’s actually a .git file pointing back at the main clone. Most modern tooling copes, but older scripts that check for the folder might not.
Finally, worktrees make it very easy to end up with six branches in flight. That’s a feature right up until it isn’t. I do a git worktree list on a Friday and remove anything I’ve merged or abandoned, otherwise the folder next to my clone slowly fills with half-remembered experiments.
Final thoughts#
If you’re running a coding agent on anything longer than a one-line fix, create a worktree for it first. One command, a sibling folder, and the agent gets a working directory you’re not going to disturb. Start with git worktree add -b and git worktree remove and ignore the rest until you need it. The next time you’d normally reach for git stash, make a worktree instead and see whether you miss the stash. I haven’t.



