warmtree: a pool of pre-warmed git worktrees for parallel coding agents

#warmtree#git#ai-agents#tooling#python

The first time I ran three coding agents against the same repo, each one needed its own checkout. Fine. git worktree add exists for exactly this and it takes about as long as a blink. Then each worktree needed npm ci, and a .env that git doesn’t track, and I sat there watching three progress bars install the same 457 packages into three folders. And especially while working on larger repositories, when I asked my local AI agents to checkout and review or locally test a 5 line fix, doing a git worktree add can take seconds to minutes. That doesn’t just waste time, that also takes me out of the flow state as well.

I automate everything that happens twice. This was happening several times a day.

The slow part isn’t git worktree add

Creating a worktree is a metadata operation. Git links a new working directory to the existing object store and checks out a branch. On a laptop that is milliseconds.

Everything after it is the problem. Dependencies have to be installed. Build caches start cold. The .env and .env.local files that make the app run locally are untracked, so they aren’t in the new tree at all. On a small project that’s a minor annoyance. On a real one it’s minutes, and with agents fanning out in parallel you pay it over and over.

I measured it on a private TypeScript app: 457 packages, 664 MB of node_modules. A cold git worktree add followed by npm ci took 6.6 seconds on a fast machine with a fast network and a warm npm cache. That is the best case. The typical case, on a corporate network with a fresh cache, is a coffee break.

warmtree take handed over a ready workspace in 0.15 seconds.

What warmtree does

warmtree keeps a small pool of worktrees already created, dependencies installed, env files copied, and parked on your base branch with a detached HEAD. It calls them slots. I was going to stay in tree metaphor-land but I didn’t want to semantically overload terms like tree-branch. When you need a workspace, you take one:

warmtree init          # writes .warmtree.toml
warmtree fill          # creates and warms the slots
cd "$(warmtree take feature/login)"
# ... commit, push, open a PR ...
warmtree release feature/login

take creates the branch inside a ready slot, prints the slot’s path, and refills the pool behind you. release parks the slot back on the base branch and returns it to the pool with its installed dependencies intact, so the next take is just as fast. So it’s not just about switching branches, it’s about calling dibs or reserving/checking-out/etc a slot and also keeping it warm, or fast-forwarded.

That’s the whole tool. warmtree owns the create and release steps and nothing else. In between, a slot is an ordinary git worktree. Your editor, your hooks, your other worktree tools, and every git command work exactly as they would anywhere else. If you already use worktrunk for hooks and port allocation and cleanup, keep using it; warmtree just replaces the create step with a warm one.

The config is one small file:

[pool]
size = 2                        # slots to keep ready
lockfiles = ["package-lock.json"]

[warm]
run = ["npm ci"]                # commands run inside a slot when it warms
copy = [".env", ".env.local"]   # untracked files copied from the main worktree

warmtree has no idea what npm is. You tell it what to run; it runs it inside each slot and remembers the hash of your lockfiles. A nightly warmtree refresh fast-forwards the idle slots and re-runs the install only where a lockfile changed.

Why this matters for agents specifically

A human developer creates a worktree a few times a week. A coding agent fanning out on a task might need three at once, right now, and it will happily run git worktree add and npm ci itself every single time.

So warmtree ships an Agent Skill: a short instruction file that warmtree init drops into the right folder for whichever agents it finds signs of in your repo, currently Claude Code, GitHub Copilot, Codex, and Cursor. The skill teaches the agent four things. Run warmtree which to check whether it’s already in a slot. Run warmtree size before fanning out to see how many are ready, and grow the pool if it needs more. Run warmtree take instead of git worktree add. Run warmtree release when the branch is merged.

Commit that folder and every agent that touches the project gets the same behavior. This post was written in slot-1 of this site’s pool, by an agent that took the slot in 0.15 seconds and found two bugs in warmtree while doing it. Dogfooding works.

Design choices

A few decisions I’m happy with:

Slots are detached, and branches are created at take time. Git allows a branch to be checked out in only one worktree. If slots parked on a named branch, the pool would collide with your main checkout. Parking them detached on the base commit means a slot is never “on” a branch anyone is using.

Release resets instead of deleting. release checks out the base branch detached and runs git clean -fd, which removes untracked files but keeps ignored ones. node_modules, .venv, and their friends survive. That is what makes the second take as fast as the first.

Warming never holds the lock. A slot is marked warming under the pool lock, the slow install runs with the lock released, and the result is recorded under the lock again. A three-minute npm ci in one slot never blocks a take on another.

Two agents can take at the same moment. State is one JSON file guarded by an OS file lock. Concurrent takes get different slots. There’s a test for it, against real git repos, because nothing about git is mocked in the suite.

An empty pool is slow, not broken. If nothing is ready, take falls back to a plain git worktree add, says so on stderr, and the new worktree joins the pool. Agents should never fail because the pool drained.

stdout is only the path. Everything else take says goes to stderr, so cd "$(warmtree take my-branch)" just works. Small thing. I use it forty times a day.

Standard library only. warmtree has to run before your project’s own dependencies exist. That’s the entire point, so it has none of its own. Python 3.12 and git, nothing else.

Already have worktrees? Adopt them

Added September 11, 2026, with warmtree 0.2.0.

The first question I got after publishing this was some version of “I already have six worktrees; do I have to throw them away?” No. The pool is additive. init and fill don’t touch anything you made by hand, and you can start using take for your next branch today while the old worktrees sit where they are.

For the ones you’re still working in, there is now adopt:

cd "$(warmtree adopt ../myrepo-feature-x)"

That moves the directory into the pool as a taken slot. The branch comes with it, so do uncommitted changes, and so do the installed dependencies. Nothing is re-installed and nothing is reset; adoption is a move, not a rebuild. When the branch is done, warmtree release recycles the slot with its node_modules intact instead of you deleting the tree, which is the whole reason to bother.

Two things to know. Because it’s a move, any editor or shell already open on the old path needs repointing, which is why adopt prints the new path on stdout and the cd "$(...)" idiom follows it. And it wants a branch checked out; a detached worktree is refused until you give it one. The main worktree can’t be adopted either, on purpose.

Branches without a checkout need no onboarding at all. They’re just refs, and warmtree take <branch> is how they get a workspace from now on. A worktree whose branch merged months ago isn’t worth adopting; delete it and let a fresh slot do the work.

The agent skill knows about adopt too, with one guardrail: since it moves directories, the skill only runs it when you ask to onboard an existing worktree, and it tells you the new path.

Status and how to try it

It’s still early for this one. I’m dogfooding it daily on my fun side projects. As of this update it’s at 0.2.0. MIT licensed, source on GitHub, package on PyPI.

uv tool install warmtree
# or, on a machine with nothing but Python and git:
uvx warmtree status
# or straight from the repo:
uv tool install git+https://github.com/dcolliervb23/warmtree

Then, from inside a repo:

warmtree init
# put your install command in [warm] run
warmtree fill
warmtree status

If something surprises you, open an issue. The papercuts found so far came from using it, not from planning it, and I’d like more of those.

exit 0

← ls ~/posts