Blog / git
Parallel agents, one database: give each worktree its own
Two coding agents in two git worktrees share one dev database, so one agent's migration breaks the other's tests. One database per worktree, in one script.
On this page
Two agents, two git worktrees, two branches. Their files are separate, so you expect them not to touch each other's work. Then one agent's tests start failing with an error about a column its branch never changed. The other agent ran a migration, and both worktrees use the same database.
Worktrees keep files apart, but the database lives outside the repository. When you set up a worktree by copying .env from the main checkout, every worktree gets the same DATABASE_URL. Every migration, seed and test cleanup then lands in the one database all the agents share.
The fix is one database per worktree, created from a template so it starts with the same tables and seed data. Below is what the collision looks like, a short script that sets it up, and how to clean up. Every command was run on PostgreSQL 17 and git 2.54.
What the collision looks like#
Here are two worktrees of one repository, both pointing at app_dev:
git worktree add ../shop-feat-a -b feat-a
git worktree add ../shop-feat-b -b feat-bThe agent in feat-a renames a column as part of its task:
alter table users rename column name to full_name;The agent in feat-b hasn't changed users at all, but its code still reads name:
ERROR: column "name" does not exist
LINE 1: select name from usersTo that agent, the failure looks like its own bug. It will try to fix it, maybe by adding a migration that renames the column back, which then breaks feat-a. Two agents can trade migrations like this for a long time before anyone notices that they share a database.
The same thing happens with less obvious changes: a seed script that truncates a table while another agent's tests run, a dropped index that makes the other branch's queries slow, or a migration tool that refuses to run because the database's history has a migration the branch doesn't know about.
One database per worktree#
PostgreSQL can create a database as a copy of another one with create database … template …. Keep a seeded app_dev as the template, and give each worktree its own copy named after its folder.
Save this as scripts/worktree-db.sh and commit it:
#!/bin/sh
# Give this worktree its own database, copied from app_dev, and point .env.local at it.
set -eu
server="postgres://postgres:dev@localhost:5432"
template="app_dev"
folder=$(basename "$(git rev-parse --show-toplevel)")
db="app_$(printf '%s' "$folder" | tr 'A-Z' 'a-z' | tr -c 'a-z0-9' '_')"
if ! psql "$server/postgres" -tAc "select 1 from pg_database where datname = '$db'" | grep -q 1; then
psql "$server/postgres" -c "create database \"$db\" template \"$template\""
fi
touch .env.local
grep -v '^DATABASE_URL=' .env.local > .env.local.new || true
echo "DATABASE_URL=$server/$db" >> .env.local.new
mv .env.local.new .env.local
echo "This worktree uses $db"Change server and template to match your setup. Then, in each worktree, run:
sh scripts/worktree-db.shIn shop-feat-a it prints:
CREATE DATABASE
This worktree uses app_shop_feat_aand .env.local now holds:
DATABASE_URL=postgres://postgres:dev@localhost:5432/app_shop_feat_aThe script is safe to run twice. If the database already exists, it skips the create and just rewrites the DATABASE_URL line. With this in place, the rename in feat-a changes only app_shop_feat_a, and select name from users in feat-b keeps working.
Two things to check for your stack:
- Which env file wins. Next.js, Vite and most dotenv setups let
.env.localoverride.env. If your framework reads only.env, write the line there instead. Either way, make sure the file is gitignored (git check-ignore .env.localprints the name if it is). - SQLite. A relative path such as
file:./dev.dbalready gives each worktree its own file. An absolute path sends every worktree to the same file, which brings back the problem.
The template must be idle#
Postgres copies a template only when nobody else is connected to it. While a dev server or an agent holds a connection to app_dev, the create fails:
ERROR: source database "app_dev" is being accessed by other users
DETAIL: There is 1 other session using the database.Two habits avoid this. Keep the template as a template: no app or agent points at app_dev itself, they all get copies. And when you change the template (a new migration on main, fresh seed data), stop whatever is connected, update app_dev, and create new copies from it.
Tell the agents#
An agent that sees a database error often reaches for the connection string it remembers, or for the main checkout's .env. Put the rule where it reads it, in CLAUDE.md for Claude Code or AGENTS.md for Codex:
## Database
- This worktree has its own database. Use DATABASE_URL from .env.local only.
- Never run migrations or seeds against app_dev or another worktree's database.
- If .env.local has no DATABASE_URL, run `sh scripts/worktree-db.sh` first.If you set worktrees up with a script or a .worktreeinclude file, call worktree-db.sh at the end of it, so the agent never starts with a shared database.
Clean up when the worktree goes#
Each copy holds a full set of tables, so drop it when you remove its worktree. with (force) (Postgres 13 and later) closes any connections still open:
psql "postgres://postgres:dev@localhost:5432/postgres" \
-c 'drop database if exists "app_shop_feat_a" with (force)'
git worktree remove ../shop-feat-aIf .env.local isn't gitignored, git worktree remove stops with "contains modified or untracked files"; add it to .gitignore rather than reaching for --force, which also deletes any uncommitted work.
Drop the database first. If an agent is still working in that worktree, its next query fails at once, so you find out before you delete its files. Before removing a worktree, check that nothing is still running in it; Clean up git worktrees without breaking a running agent shows how.
The short version#
- Worktrees keep files apart, not databases. A copied
.envpoints every worktree at the same one. - Give each worktree its own database, created from a seeded template:
create database "app_<folder>" template "app_dev". - Keep the template idle, or the copy fails with "being accessed by other users".
- Put the rule in
CLAUDE.mdorAGENTS.md, and drop the database when the worktree goes.
The same applies across a team: a teammate's agent migrating a shared staging database breaks your agent's run in exactly the same way. Give each person's agents their own databases, and save the shared one for changes everyone has agreed to. For the rest of a new worktree's setup (the ignored files, the install and a port of its own), see Git worktree missing .env and node_modules?.