Isolated Rails databases for Git worktrees

Posted . Visible to the public.

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.yml snippet.
  • 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_URL in 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 %>
Profile picture of Henning Koch
Henning Koch
Last edit
Henning Koch
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra dev (2026-10-09 08:00)