How I use Git worktrees - ReJotGit worktrees have a reputation for being inconvenient. What works for me is having static<br>worktrees. Instead of creating a new worktree for every task, I reuse a fixed pool of directories.
The other tips in this post follow from this main idea.
Keep worktrees in a fixed location
I store each project in ~/dev and its worktrees in a neighboring directory:
~/dev/my-project<br>~/dev/my-project.worktrees/<br>I usually maintain five to ten worktree directories for a project. The pool acts like a circular<br>buffer: when I finish one task, I reuse its directory for another. The number of directories is<br>arbitrary, but I find that it helps to align the number with my brain’s working memory.1
The names of these folders are arbitrary. In the VS Code window I currently have open, I see<br>directories named steps, bots, bazaar, foundation, access, and bridge. These vaguely<br>remind me of concepts within the project that these worktrees are for. Yet, they are not so specific<br>that I feel certain features have to be developed in certain worktrees. I find that these names help<br>me mentally map which features I’m working on in each tree.
Keep the main worktree clean
I reserve the main worktree as a stable checkout. Because it contains no unfinished changes, the<br>main worktree provides a reliable source for shared local files. This means I can easily cp an<br>environment file into a worktree when I set it up. When that file changes, I can distribute the<br>updated version from the main worktree to the others.
As a bonus, the stable checkout can also be used to demo the project without first cleaning up<br>in-progress features.
Give each worktree isolated local state
Each worktree should be able to run the project without conflicting with the others. SQLite makes<br>this straightforward because every worktree can keep a separate database file.
My recent projects run on Cloudflare Workers and use Durable Objects for persistence. During local<br>development, Wrangler writes SQLite state to the worktree’s .wrangler/state directory. As a<br>result, each worktree automatically gets independent local data.
PostgreSQL requires a little more setup, but the same principle applies. I create one database for<br>each reusable worktree name. The mental model of static directories helps when using psql to<br>inspect some data.
It is worth investing in seed data so that new worktrees can be easily set up. Reusing static<br>directory and database names also means that a returning worktree does not always need to start with<br>an empty database.
Let development servers select available ports
Each running worktree needs an available TCP port. Vite already handles this well enough for my<br>workflow: if port 5173 is busy, it selects the next available port.
I have considered assigning deterministic ports with 5173 + n, where n is the directory’s<br>position in a sorted list of worktree names. I have not needed that extra configuration yet.
Give each worktree its own editor window
I really like Ghostty, Superset, and Cursor, but none let me work in precisely the way I want. What<br>has worked for me is using Visual Studio Code with the “Native Tabs” feature enabled on macOS. This<br>makes it so that multiple windows open in the same physical space. VS Code has hotkeys for switching<br>between these windows, which makes everything very convenient.
I also customize the window title so that each tab shows its folder and active branch:
"window.nativeTabs": true,<br>"window.title": "${folderName}${separator}${activeRepositoryBranchName}",<br>VS Code also has some basic worktree features, but they aren’t very good, so I mostly ignore them. I<br>also do not use Copilot.
Instead, I run the Pi coding agent in an integrated terminal placed in the editor area, where files<br>normally open. A keyboard shortcut helps me open a terminal there. VS Code is good at direct code<br>navigation, finding references, etc. You know, the typical things that people who read code enjoy.
I have tried extensions that integrate Pi with VS Code, but they have not added much value. The<br>exception is a small custom Pi extension that I put together myself. It lets the agent control the<br>integrated browser, which helps with debugging.
Use tools that support worktrees
A separate node_modules directory for every worktree could consume a lot of disk space. pnpm<br>avoids duplicating most package contents by linking dependencies from its shared store. Using<br>regular old npm does not give you these benefits.
Turborepo provides a similar benefit for task outputs. Its cache lets worktrees reuse successful<br>builds and tests across worktrees.
Obviously, these examples are specific to the JavaScript ecosystem, but I’m sure the same ideas<br>apply to other ecosystems as well.
Conclusion
The key to my workflow is the fixed directory pool. Git worktrees do not need elaborate<br>orchestration.
Footnotes
I do not actively use five to ten worktrees; some can remain dormant for a while. ↩