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 a worktree-specific suffix to the database name. Developers who don't use worktrees are unaffected. Conventional Git checkouts keep their existing database names.
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.
require 'digest'
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.gsub(/[^a-z0-9_]/, '_')[0, 16]}_#{Digest::SHA256.hexdigest(git)[0, 8]}" 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.
Why truncate and hash? PostgreSQL limits database names to 63 bytes. Long worktree names can otherwise be silently truncated, potentially causing multiple databases or parallel test workers to share the same name.
Our suffix combines the first 16 characters of the readable worktree ID with an 8-character hash of its full path. This keeps names recognizable while making collisions unlikely. Assuming an application prefix of at most 25 ASCII characters, the resulting database names fit within PostgreSQL's limit, including _development.
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_a1b2c3d4_development |
myapp_feature_login_a1b2c3d4_test |
Worktree brave-panda
|
myapp_brave_panda_8e7f6a5b_development |
myapp_brave_panda_8e7f6a5b_test |
Worktree 883f
|
myapp_883f_12ab34cd_development |
myapp_883f_12ab34cd_test |
Worktree feature-add-a-really-long-payment-checkout-flow
|
myapp_feature_add_a_re_91ca82bf_development |
myapp_feature_add_a_re_91ca82bf_test |
Hash values are illustrative. The readable portions 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. - The naming scheme assumes an application database prefix of at most 25 ASCII characters. Short hashes make collisions unlikely, but cannot guarantee uniqueness.
- Existing worktrees using a previous version of this naming scheme will get new database names and need to initialize their databases again. Conventional checkouts remain unaffected.
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 (at most 25 ASCII characters).
<%
# Give Git worktrees separate databases so parallel AI agents don't conflict.
require 'digest'
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.gsub(/[^a-z0-9_]/, '_')[0, 16]}_#{Digest::SHA256.hexdigest(git)[0, 8]}" 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 %>