DEV Community

Cover image for Parallel AI Coding Agents in Git Worktrees: How to Stop the Collisions
ILIA KARPENKO
ILIA KARPENKO

Posted on Edited on

Parallel AI Coding Agents in Git Worktrees: How to Stop the Collisions

Run three AI coding agents on one machine, each in its own git worktree, and the code stays separate. Everything around the code does not.

Two dev servers try to bind port 3000, and one of them fails. Two tasks write to the same database, so one agent's test rows appear in the other agent's run. Three builds start at the same moment and the machine freezes. Finished task folders stay on disk, while package caches, browser downloads and temp files fill the drive.

Git keeps the files apart. It does not keep ports, databases, processes or disk space apart. You can fix most of this with no tool at all. Here is the manual version first.

Step 1: one worktree per task

git worktree add -b feature-login ../myapp-feature-login main
cd ../myapp-feature-login
Enter fullscreen mode Exit fullscreen mode

-b creates a new branch from main. Each task gets its own folder and branch. Your main checkout stays as it is.

Step 2: one env file per task, with its own port

Create .env.local in each worktree:

PORT=3101
DATABASE_URL=postgres://localhost/myapp_feature_login
Enter fullscreen mode Exit fullscreen mode

The next task gets PORT=3102. Check how your dev server reads its port. Some frameworks read .env.local. Others take a flag.

Step 3: one database per task, inside one Postgres server

createdb myapp_feature_login
Enter fullscreen mode Exit fullscreen mode

Or, in psql: CREATE DATABASE myapp_feature_login;. Run the migrations against that database. One server holds all the tasks. You do not start a second Postgres instance for each agent.

Step 4: run heavy commands one at a time

On Linux, flock takes an exclusive lock on a file. The kernel drops the lock when the process dies, so a crash leaves no stale lock:

flock /tmp/build.lock npm run build
Enter fullscreen mode Exit fullscreen mode

flock waits until the lock is free. Stock macOS has no flock. On macOS and in Git Bash, use a directory lock. mkdir either creates the folder or fails, so only one shell wins:

until mkdir /tmp/build.lock 2>/dev/null; do sleep 2; done
npm run build
rmdir /tmp/build.lock
Enter fullscreen mode Exit fullscreen mode

The crash caveat applies to mkdir only. If the build crashes or you close the terminal, rmdir never runs. Delete /tmp/build.lock by hand before the next build.

agentgc run uses OS file locks (flock on Unix, LockFileEx on Windows), so it has no stale lock.

What gets hard by hand

Steps 1 to 4 hold up while the task count stays small. As it grows, four chores pile up:

  • Cleanup. git worktree remove refuses while a shell or an editor sits in the folder. On Windows, the folder stays locked until you find the program that holds it.
  • Port bookkeeping. You keep a list of which port belongs to which task. A forgotten dev server keeps its port after its task ends.
  • Killing the right process. On Windows, netstat -ano | findstr :3101 shows the PID. Then run taskkill /PID <pid> /F. Check the PID first. A wrong PID stops someone else's work.
  • Dependencies. Each worktree holds its own node_modules, .venv or target folder. The disk use adds up for every task.

The same steps in agentgc

agentgc is a garbage collector for the disk junk that CLI coding agents and dev tools leave behind: package caches, browser downloads, agent sessions and temp files. It also builds task environments: one worktree, free ports, one database name per task and an env file. The task commands create all of that in one step. The run command queues heavy work, and up starts the services of a task.

Download an archive for Windows, Linux or macOS (Apple Silicon) from the latest release. Or build it from a clone of the repo with Rust: cargo install --path .

Put the task settings in agentgc.task.toml in the repo root. A short version:

[worktree]
dir = "worktrees"
base = "main"

[copy]
files = [".env.local"]

[ports]
names = ["web"]
range = [3100, 3999]

[db]
name = "{repo}_{name}"
create = "createdb {db}"
drop = "dropdb {db}"

[env.dynamic]
DATABASE_URL = "postgres://localhost/{db}"

[setup]
commands = ["npm install"]
Enter fullscreen mode Exit fullscreen mode

dir = "worktrees" puts the worktrees in a folder inside the repo, so add that folder to .gitignore. agentgc prints a hint when the folder is not ignored. It never edits .gitignore itself.

The commands:

agentgc scan                                       # read every path before any clean
agentgc task new feature-login                     # dry run: prints the plan
agentgc task new feature-login --yes --run-setup   # worktree, ports, database, setup
agentgc task list                                  # ports, database and status per task
agentgc ps                                         # processes per task, with RAM
agentgc ps --stop                                  # dry run: idle dev servers, tests, builds
agentgc run -- npm test                            # queued: heavy commands take turns
agentgc clean --dry-run                            # junk from scan, item by item
agentgc task done feature-login                    # dry run: blockers and processes
agentgc task done feature-login --yes              # removes worktree, task files, merged branch
Enter fullscreen mode Exit fullscreen mode

task new without --yes only prints the plan. The file .agentgc/task.env holds PORT_WEB and DATABASE_URL, so your dev server must read that file.

Safety model

Only two commands delete: task done --yes and clean.

  • Dry run first. task new, task done and ps --stop print a plan and change nothing until you add --yes.
  • Blockers stop task done. It refuses while the worktree holds uncommitted files or commits that are neither merged nor pushed. --force overrides both checks and prints what would be lost.
  • Item by item. clean asks about each item. A safe item needs a y. A risky item needs you to type its ID. Risky items go to the Recycle Bin, not to permanent delete.
  • Guards. clean refuses paths that hold a .git entry, and paths named Documents, Downloads or User Data, among others.
  • Idle only. ps --stop touches only idle dev servers, language servers, tests and builds. Agents, databases and other processes keep running.
  • Config runs commands. create, drop and setup.commands run as you, without a shell. Read agentgc.task.toml like a Makefile before you use --run-setup or task done --yes.

Honest limits

  • Built and covered by automated tests on Windows, Linux and macOS in GitHub Actions. Windows is the only system where the full flow was run end to end. The release binaries are not code signed.
  • agentgc up starts the services listed for a task. It is opt-in and installs no system service. Nothing starts at boot or login.
  • agentgc never starts containers, never runs Docker and never talks to a database server.
  • On Windows, ps --stop --yes stops nothing without --force, because agentgc has no graceful stop on Windows.
  • run is not fair. Whoever finds a free slot first gets it.

Proof

After one cleanup session on 2026-11-04, free space on my Windows machine went from about 6 GB to about 180 GB. This is my own statement. It is not a measurement by agentgc.

Credit

The queue idea behind run comes from heavy by mokevnin, a counting semaphore for macOS and Linux. agentgc's run is separate code. It adds Windows support and named resources, such as one phone or one browser profile.

Next step

If you run agents in parallel and want fewer collisions, star the repo: https://github.com/JohnWayneeee/agentgc

Telegram channel https://t.me/+pWGhdQdw5DZmYzYy

Top comments (6)

Collapse
 
yuhaixia profile image
Yuhai Xia •

The port + database split is the part I wish I had taken seriously earlier. I ran two agents in separate worktrees last month and still watched them fight over the same local Postgres database and a shared Redis cache — the code was isolated, but a migration from one task dropped a column the other task still expected, and I spent an evening untangling which branch had actually run it. Since then I give each worktree its own database name and port range in a small setup script, plus a one-line check that prints the active port before starting a dev server. One question: how do you handle the case where a task also needs a shared external service, like a sandbox API key with a rate limit — do you queue those calls too, or just accept that some collisions live outside git?

Collapse
 
ghost_wayne profile image
ILIA KARPENKO •

agentgc run --resource NAME --slots N -- caps how many commands on that resource run at once, across all worktrees. With --slots 1, they run one at a time. It is a concurrency cap, not a rate limit, and it only covers commands started through agentgc run. agentgc does not throttle the calls your app makes. Ports and databases already stay separate per worktree, since task new picks a free port and a database name for each task.

Collapse
 
yuhaixia profile image
Yuhai Xia •

That clears it up — --slots is a concurrency cap, not a rate limit, and it only covers commands launched through agentgc run, so the app's own calls to the shared key are still unthrottled. Same shape as the database collision I ran into: the code was isolated, the shared thing wasn't. Do you add a second layer for the key itself — a small local proxy, or per-worktree keys — or just eat the 429s and let backoff sort it out?

Collapse
 
nark3d profile image
Adam Lewis •

Prove port and database bookkeeping survive a crash, since flock and mkdir already disagree there. A lock the kernel drops does not need anyone to remember to delete a folder. Every heavy command going through it, including the ones an agent runs on its own initiative, is what keeps it there in this case. The moment one of them calls the build directly, the queue is decoration. What stops an agent going around run?

Collapse
 
reidmarlow profile image
Reid Marlow •

The mkdir lock for heavy builds is simple, but a crash leaves a dead lockfile that halts subsequent runs. On Linux, flock binds the mutex to the file descriptor instead (flock /tmp/build.lock npm run build), so the kernel releases it automatically if the agent or build aborts.

For dependencies across parallel worktrees, pnpm or uv avoids the disk hit by hardlinking against a single global store. That stops three parallel branches from chewing through 15 GB of duplicate dependencies.

Collapse
 
ghost_wayne profile image
ILIA KARPENKO •

Agreed on the lock. The manual example in the article uses mkdir, which leaves a stale lock after a crash. agentgc's run takes OS file locks instead (flock on Unix, LockFileEx on Windows), so the OS releases the lock when the holder dies. A shared pnpm or uv store is on the roadmap, but nothing is built yet. The first step is a report of how much disk it would save.