- Book: Database Playbook
- Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
An agent's first test run in its new worktree fails on an import from src/generated/. The folder exists. It lives in your main checkout, where prisma generate wrote it weeks ago, and git ignores it.
Most people running agents in worktrees learn about .env, node_modules and port 3000 on day one. Past those, git worktree add leaves seven more gaps, and each has a check and a one-line fix.
Two commands show most of the gap
A new worktree gets the tracked files of its branch. Anything git ignores, or has never seen, stays where it was. Run these two in your main checkout:
# ignored files and folders that exist here git ls-files --others --ignored --exclude-standard --directory # untracked files that aren't even ignored git ls-files --others --exclude-standard --directory collapses a whole ignored folder into one line with a trailing slash, and --exclude-standard applies .gitignore, .git/info/exclude and your global excludes (git-ls-files). Every line either command prints is something the new worktree won't have. Skip node_modules/ and build output, and the rest is roughly the list below.

A new worktree gets the tracked files. Everything else stays in the main checkout.
Git itself leaves these behind
1. Submodules are registered but empty
Check: git submodule status. A line that starts with - is a submodule that "is not initialized" (git-submodule).
Fix: git submodule update --init --recursive
--init initializes the submodules nobody has run init for yet, and --recursive repeats the work for submodules inside submodules. The git-worktree manual is blunt about this area in its BUGS section: "the support for submodules is incomplete. It is NOT recommended to make multiple checkouts of a superproject." Agents will do it anyway, so put the init in setup and check git submodule status again afterwards.
2. Git LFS files are pointers, not binaries
Check: git lfs ls-files. From the Git LFS docs: "An asterisk (*) after the OID indicates a full object, a minus (-) indicates an LFS pointer."
This happens when downloads on checkout are switched off: GIT_LFS_SKIP_SMUDGE=1 in the environment, or a machine set up with git lfs install --skip-smudge, which "requires a manual "git lfs pull" every time a new commit is checked out" (git-lfs-install), and a new worktree counts. It also happens on a machine where git lfs install never ran, since that command is what sets up the smudge filter in your global Git config.
Fix: git lfs pull, which downloads the objects for the checked-out ref and updates the working copy (git-lfs-pull).
3. git config --worktree quietly writes to every worktree
Sometimes one task needs its own setting: a commit identity for the agent, a sparse checkout, a different hooks path. By default "the repository config file is shared across all worktrees" (git-worktree). And without the extension, git config --worktree is "the same as --local" (git-config). It writes to the shared file and says nothing.
Check: git config --get extensions.worktreeConfig (no output means it's off).
Fix, once per repository, then per worktree:
git config extensions.worktreeConfig true git config --worktree user.email agent@example.com The value lands in the file that git rev-parse --git-path config.worktree prints. Two caveats from the same manual: "Older Git versions will refuse to access repositories with this extension," and if core.bare or core.worktree sits in the shared config, move it to the main worktree's config.worktree. More settings for parallel agents are in 10 git config settings to set before running coding agents in parallel worktrees.
Your build leaves these behind
4. The Python .venv is missing, and you can't copy it
uv keeps the project environment "in a .venv directory next to the pyproject.toml," and says not to commit it (uv project layout). So the new worktree has none. Copying the one from your main checkout is the wrong fix. Python's venv docs say environments are "not considered as movable or copyable – you just recreate the same environment in the target location."
Check: test -d .venv || echo "no venv"
Fix: uv sync --locked. With --locked, uv raises an error if the lockfile is out of date instead of updating it (uv sync), so an agent's setup never rewrites uv.lock behind your back. Without uv: python -m venv .venv && .venv/bin/pip install -r requirements.txt.
5. Generated code that git ignores
A Prisma client, sqlc query code, protobuf stubs. If your team keeps generated code out of git, every checkout has to regenerate it, and a worktree is a checkout. Prisma 7 makes the generator's output field required, so the client lands at a path inside your repo, often an ignored one.
Check: sqlc has sqlc diff, which compares the generated files to the existing ones. For the others, the type checker or the first build fails on the missing import.
Fix, one line per generator:
npx prisma generate # Prisma Client sqlc generate # code from your SQL queries buf generate # protoc plugins, from buf.gen.yaml buf generate reads buf.gen.yaml from the current directory by default. Prisma's docs suggest running prisma generate in postinstall. If your package.json does that, npm ci already covers the Prisma line.
Your machine leaves these behind
6. Local config files nobody committed
compose.override.yaml is the usual suspect. "By default, Compose reads two files, a compose.yaml and an optional compose.override.yaml file" (Docker docs), so it changes how your stack starts without appearing in any commit. Add config/local.toml, a local .vscode/settings.json and whatever else your team keeps off the record.
Check: the two git ls-files commands at the top. The second one lists files that are untracked but not ignored, and those are trouble for any tool that only copies ignored files (Hermes is one, below).
Fix: make them ignored for this repository without touching .gitignore, then copy them in setup. .git/info/exclude lives in the common git directory, so every worktree reads the same file (gitrepository-layout):
exclude="$(git rev-parse --git-common-dir)/info/exclude" echo "compose.override.yaml" >> "$exclude" echo "config/local.toml" >> "$exclude" 7. Docker Compose gives the worktree an empty database
Docker Compose names the project after "the name of the directory that contains the Compose file." Named volumes are scoped to that project: a volume called db-data becomes {project_name}_db-data (volumes reference). If your main checkout is a folder called app, its data sits in app_db-data. A worktree at ../fix-login gets fix-login_db-data, freshly created and empty, and the agent's tests run against a database with no tables.
Check: docker volume ls --filter name=db-data
Two tempting fixes make it worse. A shared COMPOSE_PROJECT_NAME (including one set in a .env you copy in), or a top-level name: in compose.yaml, puts every worktree in the same Compose project, running the same containers. A volume with a fixed name: "is used as is and is not scoped with the stack name," so every worktree shares one database and one agent's migration changes the schema under the others. If your compose.yaml already sets a top-level name:, grep -n '^name:' compose.yaml will tell you, and you're in the first case today.
Fix: let each worktree keep its own volume, and fill it during setup:
docker compose up -d --wait db npx prisma migrate deploy npx prisma db seed --wait waits for services to be "running|healthy" (compose up). migrate deploy "applies all pending migrations, and creates the database if it does not exist," but it doesn't seed, and the Prisma CLI reference notes that in v7 migrate reset no longer runs seed scripts either, so call db seed yourself.
All seven in one Hermes recipe
I build Hermes IDE, so weigh this section with that in mind. Hermes reads .hermes/worktree.toml from your repository and, after you approve it, runs it in each new task's worktree before the agent starts (worktree recipes). Here is a recipe for a repo that has all seven. Yours probably has two or three, so delete the lines you don't need:
# .hermes/worktree.toml setup = [ "git submodule update --init --recursive", "git lfs pull", # needs extensions.worktreeConfig, set once "git config --worktree user.email agent@example.com", "uv sync --locked", "npm ci", "npx prisma generate", "sqlc generate", "buf generate", "docker compose up -d --wait db", "npx prisma migrate deploy", "npx prisma db seed", ] copy = [".env", "compose.override.yaml", "config/local.toml"] done_when = ["npm test"] [ports] db = 5432 # HERMES_PORT_DB, for the setup commands 
The ⌘N New task sheet: Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and the "Hermes will run" preview of the claude command.
Per its docs, the first time Hermes sees the recipe, and again whenever it changes, it shows you the commands and waits: nothing runs until you choose Run setup, and Skip starts the session without it.
copy brings matching files from the project folder into the same paths and keeps any file already in the worktree. It only takes files git ignores, so if one pattern matches a tracked or non-ignored file the whole copy is refused and setup doesn't run, which is why item 6 puts both local files in .git/info/exclude first. Then setup runs in order inside the worktree (sh on macOS and Linux, cmd on Windows) until a command fails, and that failure stays in the Worktree setup card and lands in the inbox. Copied files go disk to disk, so their contents never pass through the app. If a value from a copied KEY=value line shows up in the setup log it is masked, except for keys that are never secret such as PORT and NODE_ENV, and the log is kept in memory only.
Host ports come back here. If your db service publishes a fixed one (5432:5432 is common), a second stack fails to bind and setup stops at the up line. Worse, a copied .env whose DATABASE_URL says 5432 can reach the main checkout's database, the shared-database trap from item 7. The [ports] table gives each name the first free port at or above its number that no other worktree was given, and setup commands see it as HERMES_PORT_<NAME>. Publish "${HERMES_PORT_DB:-5432}:5432" in compose.yaml and Compose picks it up during setup. The DATABASE_URL that migrate deploy, the seed and the agent's tests use still has to point at that port, and a copied .env won't do that on its own.
Each worktree now carries its own .venv, generated code and database volume, so disk use grows with every task. The Review Desk's Worktrees tab lists every Hermes worktree with its branch, task, age and size on disk.

The Review Desk's Worktrees tab: one Hermes worktree each for orders-api, web-app and orders-types, with each worktree's hermes/ branch, task, age and disk size, and the free disk space.
Docker volumes don't live in the worktree folder, so removing the worktree leaves them behind. Before you remove one, run docker compose down -v in it, which removes the named volumes declared in the Compose file (compose down).
Hermes is free, source-available under BSL 1.1, and runs on macOS, Windows and Linux. The current release is 2.3.4, and Hermes is not affiliated with Nous Research, Anthropic, OpenAI or any agent vendor. Download it, or read the code on GitHub.
If this was useful
Item 7 is the one that eats an afternoon: an agent testing against an empty database, or against one it shares with every other agent. Every worktree now builds your schema from migrations and seeds it from scratch, which is when the store you picked, and how you migrate it, starts to show. My Database Playbook is the short guide to choosing that store for each system you build. To try the recipe, commit it as .hermes/worktree.toml and start a task with ⌘N (Ctrl+Shift+N on Windows and Linux) in Hermes.

