Rails::Worktrees
rails-worktrees adds Rails-friendly bin/wt and bin/ob commands for creating Git worktrees and opening the right local browser URL for each worktree.
Requirements
- Ruby >= 3.2.0
- Rails >= 7.1, < 8.2
- Git
Installation
bundle add rails-worktrees --group development
bin/rails generate worktrees:install
# or, to also generate bin/ob without the yolo follow-ups:
bin/rails generate worktrees:install --browser
# or, to apply the common Procfile.dev + Puma + mise follow-ups automatically:
bin/rails generate worktrees:install --yoloThe generated initializer writes app config under Rails.application.config.x.rails_worktrees, so the file stays safe to load even in environments like test where the gem is only bundled for :development. When rails-worktrees is loaded, the gem applies those settings for both the CLI and in-process Rails usage.
The installer adds:
-
bin/wt— a thin wrapper that executes the gem-owned CLI -
bin/ob— an optional browser helper generated by--browseror--yolo -
config/initializers/rails_worktrees.rb— optional configuration -
Procfile.dev.worktree.example— a copy-paste helper for${DEV_PORT:-3000}inProcfile.devon regular installs - a safe update to
config/database.ymlfor common development/test database names
With --yolo, the installer also:
- generates
bin/ob - skips
Procfile.dev.worktree.example - replaces the existing
web:entry inProcfile.devwith the DEV_PORT-aware command whenProcfile.devalready exists - updates
config/puma.rbto useport ENV['DEV_PORT'] || ENV.fetch('PORT', 3000)when it still uses a supported defaultPORTbinding - updates
mise.tomlor.mise.tomlto load.envfrom[env]when either file already exists
Usage
bin/wt # auto-pick a name from bundled *.txt lists
bin/wt my-feature # use an explicit worktree name
bin/wt --skip-setup my-feature # create now, run setup later
bin/wt --dry-run my-feature # preview the full setup without changing anything
bin/wt --print-env my-feature # preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
bin/wt setup # rerun setup for the current checkout/worktree
bin/wt setup my-feature # rerun setup for a managed worktree by name
bin/wt setup ../my-project.worktrees/my-feature # setup a specific checkout without cd-ing into it
bin/wt doctor # audit install/config drift and basic worktree health
bin/wt update --dry-run # preview safe maintenance fixes
bin/wt update # apply safe maintenance fixes for managed files
bin/wt remove my-feature # remove a worktree and delete its local branch
bin/wt delete my-feature # alias for `bin/wt remove`
bin/wt remove --force my-feature # also delete an unmerged local branch
bin/wt prune # remove merged worktrees created by wt
bin/ob # open http://localhost:$DEV_PORT/
bin/ob contact # open http://localhost:$DEV_PORT/contact
bin/ob '/contact?from=nav' # open a local route with query params
bin/ob --print-url '?from=nav' # print the resolved URL without opening a browserOptions
| Flag | Description |
|---|---|
-h, --help
|
Show the help message |
-v, --version
|
Show the script version |
--dry-run [name] |
Preview worktree creation or cleanup without changing anything |
--skip-setup |
Create a worktree without running setup steps |
--force |
Force branch deletion for bin/wt remove / bin/wt delete
|
--env, --print-env <name>
|
Preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
Default behavior
By default bin/wt:
- creates a sibling directory next to your app:
workspace/my-project.worktrees/<name> - uses a branch named
🚂/<name> - creates new branches from
origin's default branch - auto-picks names from bundled
.txtfiles when no explicit name is given - retires bundled names so they are not picked twice
- bootstraps a worktree-local
.envwith deterministicDEV_PORTandWORKTREE_DATABASE_SUFFIXvalues - runs setup automatically after creation: credential linking,
bundle install,yarn installwhen applicable, bothdb:preparesteps, test asset precompile, and a finalbin/rails assets:clobber
workspace/
├── my-project/
└── my-project.worktrees/
├── feature-auth/
├── bugfix-123/
└── experiment/
WT_WORKSPACES_ROOT or config.workspace_root overrides the destination root and uses the layout <root>/<project>/<name>.
Setup command
bin/wt setup reruns setup for the current checkout. Run it from inside a linked worktree created by bin/wt, a worktree created manually with git worktree, or a checkout prepared by another tool.
If the checkout was created by bin/wt, you can also point at it by managed worktree name from the main app checkout:
bin/wt setup my-featureIf you do not want to cd first, pass an explicit checkout path:
bin/wt setup ../my-project.worktrees/my-featureThis is the recovery path when you want to create first and bootstrap later:
bin/wt --skip-setup my-feature
cd ../my-project.worktrees/my-feature
bin/wt setupbin/wt setup --dry-run previews the same steps without changing files.
Cleanup commands
bin/wt also supports cleanup commands for worktrees it manages:
-
bin/wt remove <name>— remove the named worktree and delete its local branch -
bin/wt delete <name>— alias forbin/wt remove <name> -
bin/wt prune— remove merged worktrees created bybin/wtin bulk
bin/wt remove and bin/wt prune can be run from the main checkout or any sibling worktree in the same repository family.
bin/wt remove refuses to remove the worktree you're currently in, and by default only deletes a local branch after confirming it is already merged into origin's default branch. Use bin/wt remove --force <name> when you intentionally want to delete an unmerged local branch too.
bin/wt prune only targets linked worktrees created by bin/wt whose local branches are already merged into origin's default branch. It skips the main checkout, skips the checkout you're currently in, and asks for confirmation before deleting the batch.
If you want to preview a single removal first, use --dry-run with bin/wt remove:
bin/wt remove --dry-run feature-authIf you want to see what bin/wt prune would clean up before saying yes, use --dry-run:
bin/wt prune --dry-runMaintenance commands
bin/wt also includes a small maintenance surface for installer drift and checkout health:
-
bin/wt doctor— audit managed files plus basic Git/worktree health without changing anything -
bin/wt update --dry-run— preview safe file-based fixes -
bin/wt update— apply safe file-based fixes for supported managed files
bin/wt doctor checks the generated initializer, bin/wt, optional bin/ob, supported config files such as config/database.yml, Procfile.dev, config/puma.rb, and mise.toml/.mise.toml, plus basic repository/worktree conditions like resolving origin's default branch and spotting stale registered worktree paths.
bin/wt update is intentionally conservative: it only applies safe file-based fixes for managed templates and supported config shapes. It does not delete branches, remove worktrees, or rewrite ambiguous custom files automatically.
Interactive prompts
bin/wt handles several edge cases interactively:
- Branch already exists locally — asks whether to attach a new worktree to it
- Branch already exists on origin — asks whether to create a local tracking worktree
- Target directory already exists with matching branch — asks whether to reuse it
- Target directory already exists with a different branch — asks whether to remove and recreate it
-
Prune found merged worktrees created by
wt— asks whether to delete the batch -
Retired bundled name used explicitly — rejects it and suggests running
wtwith no argument
Name validation
Worktree names must not contain / or whitespace, must not be . or .., and must be a valid Git ref component.
Configuration
The installer generates config/initializers/rails_worktrees.rb where you can override:
The initializer stays app-owned and gem-agnostic; rails-worktrees reads those settings whenever the gem is present in the current bundle groups.
| Option | Default | Description |
|---|---|---|
bootstrap_env |
true |
Write .env when creating a worktree |
workspace_root |
nil |
Override the destination root (sibling layout when nil) |
dev_port_range |
3000..3999 |
Port range for deterministic DEV_PORT allocation |
branch_prefix |
🚂 |
Prefix for worktree branch names |
name_sources_path |
bundled names/
|
Directory containing .txt name lists |
used_names_file |
~/.local/state/rails-worktrees/used-names.tsv |
TSV tracking retired names |
worktree_database_suffix_max_length |
18 |
Max length for generated database suffixes |
Database naming
The installer adds WORKTREE_DATABASE_SUFFIX to common development and test database names in config/database.yml. For a multi-database app, the target shape is:
development:
primary:
database: my_app_development<%= ENV.fetch('WORKTREE_DATABASE_SUFFIX', '') %>_primary
test:
primary:
database: my_app_test<%= ENV.fetch('WORKTREE_DATABASE_SUFFIX', '') %>_primaryIf your database.yml is too custom to patch safely, the installer leaves it alone and tells you what to update manually.
Environment bootstrap
When bin/wt creates a worktree it writes a worktree-local .env with:
-
DEV_PORT— deterministic port derived from the worktree name via CRC32, rotated throughdev_port_range, skipping ports already claimed by peer worktrees -
WORKTREE_DATABASE_SUFFIX— derived from the best available worktree identity (managed name when known, otherwise the current branch or checkout basename). When a readable suffix is already claimed by a peer checkout,bin/wtappends a shortDEV_PORT-based token to keep the databases isolated.
Existing .env values are never overwritten.
Browser helper
If you install with --browser or --yolo, the installer generates bin/ob.
bin/ob reads DEV_PORT from the current app or worktree's .env, falls back to ENV['DEV_PORT'], then falls back to 3000, and opens http://localhost:$DEV_PORT/... in your default browser.
Route examples:
bin/ob
bin/ob contact
bin/ob admin/users
bin/ob '/contact?from=footer'
bin/ob --print-url '?from=footer'bin/ob accepts a single optional local route argument. Query-only routes such as ?from=footer resolve to the app root, and full URLs such as https://example.com are intentionally rejected.
If DEV_PORT is missing from .env, bin/ob tells you when it falls back to ENV['DEV_PORT'] or 3000.
Browser opening currently uses:
-
openon macOS -
xdg-openon Linux and other Unix-like environments that provide it
For scripts and debugging, bin/ob --print-url [route] prints the resolved localhost URL without opening a browser.
By default, the installer does not edit your Procfile.dev, config/puma.rb, or mise config. It generates Procfile.dev.worktree.example with a ready-to-copy line:
web: env RUBY_DEBUG_OPEN=true bin/rails server -b 0.0.0.0 -p ${DEV_PORT:-3000}
If you run bin/rails generate worktrees:install --yolo, the installer applies the three common follow-ups for you when the files already exist:
- skip generating
Procfile.dev.worktree.example - replace the existing
web:entry inProcfile.dev - update
config/puma.rbtoport ENV['DEV_PORT'] || ENV.fetch('PORT', 3000)when it still uses a supported defaultPORTbinding - add
_.file = ".env"to the[env]section ofmise.tomlor.mise.toml
On a regular install, the follow-up message also suggests the same config/puma.rb edit when Puma still uses the default PORT binding.
Use a project-local env loader like mise with _.file = ".env" to keep values scoped per-worktree.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
Smoke testing
RSpec remains the main automated test suite. For installer and integration changes, you can also run a disposable Rails app smoke test:
bundle exec rake smoke_testThis smoke test:
- creates a temporary Rails app from a compatible Rails version
- installs
rails-worktreesfrom the current checkout path - runs
bin/rails generate worktrees:install --yolo - verifies
bin/wt,bin/ob, the generated initializer, that--yoloskips the Procfile example, yolo updates toProcfile.dev,config/puma.rb, andmise.toml,config/database.ymlpatching, and worktree.envbootstrapping - creates a temporary bare
origin, confirmsbin/wt smoke-branchcreates a real worktree, and confirmsbin/wt remove smoke-branchcan remove that merged worktree from a sibling worktree checkout
By default, the script cleans up all temp directories after the run. Set KEEP_SMOKE_TEST_ARTIFACTS=1 to keep them around for debugging, or set RAILS_WORKTREES_SMOKE_RAILS_VERSION to try a different compatible Rails version.
There is also a manually triggered GitHub Actions workflow named Smoke Test for running the same disposable-app verification in CI without slowing down the default pull request checks.
The workflow accepts optional ruby_version, rails_version, keep_artifacts, and verbose inputs. Enable keep_artifacts when you want the disposable app, bare origin, and worktree directories uploaded from CI for debugging, and enable verbose when you want shell tracing in the smoke-test log.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/asjer/rails-worktrees.
License
The gem is available as open source under the terms of the MIT License.