AI coding agents like Claude Code, Codex, and Cursor can work on multiple tasks in parallel by creating separate Git worktrees. Each worktree has its own working directory, but Rails will normally use the same development and test databases across all worktrees.
This can cause conflicts when agents run tests, change the database schema, or modify test data simultaneously.
Giving worktrees their own database
We can solve this with a small addition to database.yml that automatically detects Git worktrees and adds their Git worktree ID to the database name. Developers who don't use worktrees are unaffected. Conventional Git checkouts keep their existing database names.
For worktree-specific database, add the following ERB block at the beginning of your existing config/database.yml:
<%
# Give Git worktrees separate databases so parallel AI agents don't conflict.
git = File.join((Rails.root rescue Dir.pwd).to_s, '.git')
id = File.read(git)[%r{/worktrees/([^/\n]+)\s*\z}, 1] if File.file?(git)
worktree_suffix = "_" + id.underscore if id
%>
Then insert the suffix into your existing development and test database names:
development:
database: myapp<%= worktree_suffix %>_development
test:
database: myapp<%= worktree_suffix %>_test<%= ENV['TEST_ENV_NUMBER'] %>
Keep your existing adapter, credentials, and other database settings. A complete database.yml example is included below.
The detection reads the worktree's .git file, which points to its Git metadata. No Git subprocess is required. The underscore method requires ActiveSupport.
Example database names
Assuming the application normally uses myapp_development and myapp_test:
| Checkout | Development database | Test database |
|---|---|---|
| Normal checkout | myapp_development |
myapp_test |
Worktree feature-login
|
myapp_feature_login_development |
myapp_feature_login_test |
Worktree brave-panda
|
myapp_brave_panda_development |
myapp_brave_panda_test |
Worktree 883f
|
myapp_883f_development |
myapp_883f_test |
The names are based on Git's internal worktree IDs, which may differ from branch names or directory names.
This works regardless of which AI tool created the worktree. It also works with manually created Git worktrees and doesn't require installing additional tools.
Caveats
- Database names are isolated, but databases aren't automatically created or migrated. Every new worktree needs its own database setup. You can tell your coding agents to do that for you.
- Databases may remain after a worktree is deleted and can be cleaned up manually.
- Your project might use other storage services (like Redis). These are not isolated with this
database.ymlsnippet. - Git worktree IDs are unique within a repository, but converting them to snake_case can theoretically cause name collisions.
Tell your coding agents
Coding agents may be surprised when a new worktree begins without a database. They will recover from this, but you can save reasoning roundtrips by telling your agent beforehand.
Add this rule to your project's AGENTS.md or CLAUDE.md:
Each Git worktree has its own development and test databases. After creating a worktree, run `bin/rails db:prepare` to initialize both.
If you're feeling lucky, you can also tell your agent to delete worktree databases when they remove a worktree.
Appendix: Complete database.yml
For reference, here is a complete PostgreSQL database.yml that combines worktree isolation with some additional defaults we commonly use:
- Environment variables for database credentials and host
- Support for
DATABASE_URLin CI - Database access from Claude Code sandboxes
- Connection pooling based on
RAILS_MAX_THREADS - A 30-second PostgreSQL statement timeout
- Separate test databases for parallel test workers
Replace myapp with your application's database prefix.
<%
# Give Git worktrees separate databases so parallel AI agents don't conflict.
git = File.join((Rails.root rescue Dir.pwd).to_s, '.git')
id = File.read(git)[%r{/worktrees/([^/\n]+)\s*\z}, 1] if File.file?(git)
worktree_suffix = "_" + id.underscore if id
%>
# Allow CI to control database settings via ENV variable
<% unless ENV['DATABASE_URL'] %>
default: &default
adapter: postgresql
encoding: unicode
username: <%= ENV['DATABASE_USER'] %>
password: <%= ENV['DATABASE_PASSWORD'] %>
# https://makandracards.com/makandra/626587-claude-code-sandbox#section-database-access-sandbox
host: <%= ENV.fetch('DATABASE_HOST', 'localhost') unless ENV['SANDBOX_RUNTIME'] %>
# See https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>
variables:
# See https://makandracards.com/makandra/623000-timeouts-long-running-sql-queries
statement_timeout: 30s
development:
<<: *default
database: myapp<%= worktree_suffix %>_development
test:
<<: *default
database: myapp<%= worktree_suffix %>_test<%= ENV['TEST_ENV_NUMBER'] %>
<% end %>