<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Developer-Tooling on Damian Galarza | Software Engineering &amp; AI Consulting</title><link>https://www.damiangalarza.com/tags/developer-tooling/</link><description>Recent posts from Damian Galarza | Software Engineering &amp; AI Consulting</description><generator>Hugo</generator><language>en-us</language><managingEditor>Damian Galarza</managingEditor><atom:link href="https://www.damiangalarza.com/tags/developer-tooling/feed.xml" rel="self" type="application/rss+xml"/><item><title>Extending Claude Code Worktrees for True Database Isolation</title><link>https://www.damiangalarza.com/posts/2026-03-10-extending-claude-code-worktrees-for-true-database-isolation/</link><pubDate>Tue, 10 Mar 2026 00:00:00 -0400</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2026-03-10-extending-claude-code-worktrees-for-true-database-isolation/</guid><description>Claude Code's native worktree support handles file isolation, but Rails apps need database isolation too. Here's how to extend it with the WorktreeCreate hook.</description><content:encoded><![CDATA[<p>If you&rsquo;re running multiple Claude Code sessions on a Rails app, you need isolation. Without it, agents edit the same files, collide on the same branch, and corrupt each other&rsquo;s state.</p>
<p>Git worktrees solve the file side of this. Each agent gets its own working directory with its own branch. Anthropic recently shipped native worktree support in Claude Code, which handles the Git mechanics automatically. No external scripts to maintain.</p>
<p>But file isolation is only half the problem. A Rails app running in a worktree still points at the same development and test databases as your main checkout. Two agents running specs in parallel will insert conflicting test data, and you&rsquo;ll get flaky tests with no obvious cause.</p>
<p>Native worktrees handle file isolation. The <code>WorktreeCreate</code> hook handles everything else.</p>
<h2 id="what-native-worktree-support-does">What Native Worktree Support Does</h2>
<p>Claude Code now manages worktrees directly. Start a session with the <code>--worktree</code> flag:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>claude --worktree my-feature-branch
</span></span></code></pre></div><p>This creates a new branch and checks it out in a dedicated directory under <code>.claude/worktrees/</code>. The branch name argument is optional. Skip it and Claude generates a random name.</p>
<p>The <code>--worktree</code> flag isn&rsquo;t the only entry point. You can also:</p>
<ul>
<li>Ask Claude mid-session to work in a worktree</li>
<li>Spawn sub-agents that each get their own worktree automatically</li>
<li>Set <code>isolation: &quot;worktree&quot;</code> on custom agent definitions so they always run isolated</li>
</ul>
<p>That last option is worth calling out. If you have an agent that runs specs or does anything destructive, setting isolation at the agent level means you never have to remember the flag. It&rsquo;s just how that agent works.</p>
<p>One thing to note: <code>.claude/worktrees/</code> is not added to <code>.gitignore</code> by default. Add it early so worktree directories don&rsquo;t end up in your commit history.</p>
<h2 id="the-worktreeinclude-file">The <code>.worktreeinclude</code> File</h2>
<p>Git worktrees only duplicate tracked files. Anything in your <code>.gitignore</code> won&rsquo;t carry over. For most Rails apps, that means your <code>.env</code> file, <code>config/master.key</code>, and any other credentials are missing in every new worktree.</p>
<p>Claude Code solves this with <code>.worktreeinclude</code>. It works like a reverse <code>.gitignore</code>. You list the gitignored files that should be copied into each new worktree:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-text" data-lang="text"><span style="display:flex;"><span># .worktreeinclude
</span></span><span style="display:flex;"><span>.env
</span></span><span style="display:flex;"><span>config/master.key
</span></span><span style="display:flex;"><span>config/credentials/development.key
</span></span></code></pre></div><p>When Claude creates a worktree, it reads this file and copies each listed file from the main checkout into the new directory. Your agents stop failing silently because they&rsquo;re running without credentials.</p>
<h2 id="where-native-worktrees-fall-short">Where Native Worktrees Fall Short</h2>
<p>File isolation is necessary, but it&rsquo;s not sufficient. Consider this scenario: two agents running your test suite at the same time. Both point at the same test database. Agent A inserts test data. Agent B&rsquo;s assertions fail because the data doesn&rsquo;t match what it expected.</p>
<p>You get flaky tests, data collisions, and corrupted state. The root cause is invisible because each agent&rsquo;s test run passes when run alone.</p>
<p>True isolation for a Rails app requires three things:</p>
<ol>
<li><strong>A separate database per worktree</strong> so test runs don&rsquo;t collide</li>
<li><strong>Environment config</strong> (credentials, <code>.env</code> files) available in each worktree</li>
<li><strong>Dependencies installed</strong> so the app can actually boot</li>
</ol>
<p>The <code>.worktreeinclude</code> file handles environment config. Native worktrees handle the Git mechanics. But nothing handles the database. That&rsquo;s the gap.</p>
<h2 id="the-worktreecreate-hook">The <code>WorktreeCreate</code> Hook</h2>
<p>Claude Code supports lifecycle hooks that run at specific moments during a session. The one we care about is <code>WorktreeCreate</code>. It fires every time a worktree is created, whether from the CLI flag, a mid-session request, or a sub-agent spawn.</p>
<p>This hook was originally designed for teams using version control systems other than Git, like SVN or Mercurial. It&rsquo;s an extension point that lets you replace the default Git worktree behavior entirely with your own setup logic.</p>
<p>We can use that same extension point to bootstrap a full Rails environment.</p>
<h3 id="hook-configuration">Hook Configuration</h3>
<p>Add the hook to your Claude Code settings at <code>.claude/settings.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">&#34;hooks&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">&#34;WorktreeCreate&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;command&#34;</span>: <span style="color:#a6e3a1">&#34;.claude/hooks/worktree-create.sh&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;timeout&#34;</span>: <span style="color:#fab387">60000</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The timeout is set to 60 seconds. <code>bin/setup</code> can take a while, especially if it&rsquo;s running database migrations.</p>
<p>When the hook fires, Claude passes JSON to stdin with the session context, including the working directory and session ID. Your script reads that input, does the setup, and prints the path to the created worktree to stdout. That&rsquo;s the contract: print the path, exit zero, and Claude uses that directory.</p>
<h3 id="the-setup-script">The Setup Script</h3>
<p>Here&rsquo;s the full script. I&rsquo;ll walk through each section below.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span><span style="color:#89dceb">set</span> -euo pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># WorktreeCreate hook: creates a git worktree, symlinks shared files,</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># configures worktree-specific databases, and runs bin/setup.</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">#</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Input (JSON on stdin): { &#34;name&#34;: &#34;&lt;slug&gt;&#34;, &#34;cwd&#34;: &#34;&lt;project-root&gt;&#34;, ... }</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Output (stdout): absolute path to the created worktree directory</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># All other output goes to stderr so it doesn&#39;t interfere with the path.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">INPUT</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span>cat<span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">NAME</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$INPUT</span><span style="color:#a6e3a1">&#34;</span> | jq -r <span style="color:#a6e3a1">&#39;.name&#39;</span><span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">PROJECT_DIR</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$INPUT</span><span style="color:#a6e3a1">&#34;</span> | jq -r <span style="color:#a6e3a1">&#39;.cwd&#39;</span><span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">WORKTREE_DIR</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$PROJECT_DIR</span><span style="color:#a6e3a1">/.claude/worktrees/</span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># 1. Create the git worktree</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">if</span> <span style="color:#89dceb;font-weight:bold">[</span> -d <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">]</span>; <span style="color:#cba6f7">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Worktree directory already exists: </span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#89dceb">exit</span> <span style="color:#fab387">1</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Creating git worktree at </span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1"> ...&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>git -C <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$PROJECT_DIR</span><span style="color:#a6e3a1">&#34;</span> worktree add -b <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> HEAD &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># 2. Symlink entries from .worktreeinclude</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">INCLUDE_FILE</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$PROJECT_DIR</span><span style="color:#a6e3a1">/.worktreeinclude&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">if</span> <span style="color:#89dceb;font-weight:bold">[</span> -f <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$INCLUDE_FILE</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">]</span>; <span style="color:#cba6f7">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Symlinking entries from .worktreeinclude ...&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">while</span> <span style="color:#f5e0dc">IFS</span><span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">read</span> -r entry <span style="color:#89dceb;font-weight:bold">||</span> <span style="color:#89dceb;font-weight:bold">[</span> -n <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$entry</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">]</span>; <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Skip blank lines and comments</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">entry</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$entry</span><span style="color:#a6e3a1">&#34;</span> | sed <span style="color:#a6e3a1">&#39;s/#.*//&#39;</span> | xargs<span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">[</span> -z <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$entry</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">]</span> <span style="color:#89dceb;font-weight:bold">&amp;&amp;</span> <span style="color:#cba6f7">continue</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">SOURCE</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$PROJECT_DIR</span><span style="color:#a6e3a1">/</span><span style="color:#f5e0dc">$entry</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">TARGET</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">/</span><span style="color:#f5e0dc">$entry</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">if</span> <span style="color:#89dceb;font-weight:bold">[</span> ! -e <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$SOURCE</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">]</span>; <span style="color:#cba6f7">then</span>
</span></span><span style="display:flex;"><span>      <span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;  SKIP (not found): </span><span style="color:#f5e0dc">$entry</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>      <span style="color:#cba6f7">continue</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Ensure parent directory exists in the worktree</span>
</span></span><span style="display:flex;"><span>    mkdir -p <span style="color:#a6e3a1">&#34;</span><span style="color:#cba6f7">$(</span>dirname <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$TARGET</span><span style="color:#a6e3a1">&#34;</span><span style="color:#cba6f7">)</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Remove the file/dir that git checkout placed there (if any)</span>
</span></span><span style="display:flex;"><span>    rm -rf <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$TARGET</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    ln -s <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$SOURCE</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$TARGET</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;  Linked: </span><span style="color:#f5e0dc">$entry</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">done</span> &lt;<span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$INCLUDE_FILE</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">else</span>
</span></span><span style="display:flex;"><span>  <span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;No .worktreeinclude file found, skipping symlinks.&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># 3. Configure worktree-specific databases via .env.local</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Sanitize the name for use in database names (replace dashes with underscores)</span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">DB_SLUG</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1">&#34;</span> | tr <span style="color:#a6e3a1">&#39;-&#39;</span> <span style="color:#a6e3a1">&#39;_&#39;</span><span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">ENV_LOCAL</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">/.env.local&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Writing worktree-specific database config to .env.local ...&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>cat &gt;<span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$ENV_LOCAL</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#a6e3a1">&lt;&lt;EOF
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1"># Auto-generated for worktree: $NAME
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">DB_DATABASE=tracewell_development_${DB_SLUG}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">DB_TEST_DATABASE=tracewell_test_${DB_SLUG}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">EOF</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;  Development DB: tracewell_development_</span><span style="color:#a6e3a1">${</span><span style="color:#f5e0dc">DB_SLUG</span><span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;  Test DB:        tracewell_test_</span><span style="color:#a6e3a1">${</span><span style="color:#f5e0dc">DB_SLUG</span><span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Output: print the worktree path for Claude Code (must happen before</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># any step that could fail so Claude Code can track the session)</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># 4. Run bin/setup (skip starting the dev server)</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Running bin/setup --skip-server ...&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#89dceb">cd</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">&amp;&amp;</span> bin/setup --skip-server<span style="color:#89dceb;font-weight:bold">)</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span></code></pre></div><p>The script does four things:</p>
<p><strong>Creates the Git worktree.</strong> This replicates what Claude does natively, giving us a new branch and working directory.</p>
<p><strong>Copies <code>.worktreeinclude</code> files.</strong> We re-implement this behavior since we&rsquo;re replacing the default worktree creation. Each listed file gets copied from the main checkout into the new worktree.</p>
<p><strong>Writes a <code>.env.local</code> with unique database names.</strong> This is the key part. The branch name gets transformed into a Postgres-safe format (hyphens become underscores) and used as a prefix for both the development and test database names. Your <code>database.yml</code> needs to read from these environment variables, falling back to defaults when they&rsquo;re not set:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># config/database.yml</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">development</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">url</span>: &lt;%= ENV.fetch(&#34;DATABASE_URL&#34;, &#34;postgres://localhost/myapp_development&#34;) %&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">test</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">url</span>: &lt;%= ENV.fetch(&#34;TEST_DATABASE_URL&#34;, &#34;postgres://localhost/myapp_test&#34;) %&gt;
</span></span></code></pre></div><p>When <code>.env.local</code> exists in the worktree, the app picks up the unique database names. In your main checkout, the defaults apply as usual.</p>
<p><strong>Runs <code>bin/setup</code>.</strong> This creates the databases, runs migrations, and installs dependencies. The worktree is fully bootable when the hook finishes.</p>
<p>Each worktree now points at its own development and test database. Two agents can run specs simultaneously without interference.</p>
<h2 id="cleaning-up-with-worktreeremove">Cleaning Up with <code>WorktreeRemove</code></h2>
<p>Worktrees accumulate, and so do the databases they create. Claude Code automatically cleans up the worktree directory when you exit a session with no changes, but the databases stick around.</p>
<p>The <code>WorktreeRemove</code> hook mirrors the creation logic in reverse:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">&#34;hooks&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">&#34;WorktreeCreate&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;command&#34;</span>: <span style="color:#a6e3a1">&#34;.claude/hooks/worktree-create.sh&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;timeout&#34;</span>: <span style="color:#fab387">60000</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">&#34;WorktreeRemove&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;command&#34;</span>: <span style="color:#a6e3a1">&#34;.claude/hooks/worktree-remove.sh&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;timeout&#34;</span>: <span style="color:#fab387">60000</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The removal script reads the worktree path, derives the database names the same way the creation script did, and drops them:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span><span style="color:#89dceb">set</span> -euo pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># WorktreeRemove hook: drops worktree-specific databases and deletes the</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># associated branch. Claude Code handles git worktree removal automatically.</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">#</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Input (JSON on stdin): { &#34;name&#34;: &#34;&lt;slug&gt;&#34;, &#34;cwd&#34;: &#34;&lt;project-root&gt;&#34;, ... }</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># All output goes to stderr.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">INPUT</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span>cat<span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">NAME</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$INPUT</span><span style="color:#a6e3a1">&#34;</span> | jq -r <span style="color:#a6e3a1">&#39;.name&#39;</span><span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">PROJECT_DIR</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$INPUT</span><span style="color:#a6e3a1">&#34;</span> | jq -r <span style="color:#a6e3a1">&#39;.cwd&#39;</span><span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span><span style="color:#f5e0dc">WORKTREE_DIR</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#cba6f7">$(</span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$INPUT</span><span style="color:#a6e3a1">&#34;</span> | jq -r <span style="color:#a6e3a1">&#39;.worktree_path&#39;</span><span style="color:#cba6f7">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">if</span> <span style="color:#89dceb;font-weight:bold">[</span> ! -d <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">]</span>; <span style="color:#cba6f7">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Worktree directory not found: </span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#89dceb">exit</span> <span style="color:#fab387">0</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Move to main repo before removing the worktree so the process cwd stays valid</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">cd</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$PROJECT_DIR</span><span style="color:#a6e3a1">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># 1. Drop worktree-specific databases via Rails</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Dropping databases for worktree: </span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1"> ...&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#89dceb">cd</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">&amp;&amp;</span> <span style="color:#f5e0dc">RAILS_ENV</span><span style="color:#89dceb;font-weight:bold">=</span>development bin/rails db:drop <span style="color:#f5e0dc">DISABLE_DATABASE_ENVIRONMENT_CHECK</span><span style="color:#89dceb;font-weight:bold">=</span>1<span style="color:#89dceb;font-weight:bold">)</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#89dceb">cd</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> <span style="color:#89dceb;font-weight:bold">&amp;&amp;</span> <span style="color:#f5e0dc">RAILS_ENV</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#89dceb">test</span> bin/rails db:drop <span style="color:#f5e0dc">DISABLE_DATABASE_ENVIRONMENT_CHECK</span><span style="color:#89dceb;font-weight:bold">=</span>1<span style="color:#89dceb;font-weight:bold">)</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># 2. Deregister the git worktree (required before branch can be deleted)</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Deregistering git worktree at </span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1"> ...&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>git -C <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$PROJECT_DIR</span><span style="color:#a6e3a1">&#34;</span> worktree remove --force <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$WORKTREE_DIR</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># 3. Delete the branch</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># -------------------------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">if</span> git -C <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$PROJECT_DIR</span><span style="color:#a6e3a1">&#34;</span> rev-parse --verify <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1">&#34;</span> &gt;/dev/null 2&gt;&amp;1; <span style="color:#cba6f7">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Deleting branch: </span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1"> ...&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span>  git -C <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$PROJECT_DIR</span><span style="color:#a6e3a1">&#34;</span> branch -D <span style="color:#a6e3a1">&#34;</span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">else</span>
</span></span><span style="display:flex;"><span>  <span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Branch not found, skipping: </span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1">&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">echo</span> <span style="color:#a6e3a1">&#34;Worktree &#39;</span><span style="color:#f5e0dc">$NAME</span><span style="color:#a6e3a1">&#39; removed successfully.&#34;</span> &gt;&amp;<span style="color:#fab387">2</span>
</span></span></code></pre></div><p>Alternatively, you could skip the hook and run a periodic cleanup script that finds orphaned databases and drops them. The hook approach is cleaner if you want everything automated.</p>
<h2 id="beyond-rails">Beyond Rails</h2>
<p>These patterns aren&rsquo;t Rails-specific. Rails conventions make the setup straightforward, but any application with external state has the same gap. Django, Phoenix, Laravel, if your app talks to a database, a cache, or a message queue, Git worktrees alone won&rsquo;t isolate those resources.</p>
<p>The worktree gives you file isolation. The hook gives you everything else.</p>
<p>The mental model is simple: the <code>WorktreeCreate</code> hook is your opportunity to bootstrap whatever environment your application needs, and the <code>WorktreeRemove</code> hook is your opportunity to tear it down. What you put in those scripts depends entirely on your stack.</p>
<hr>
<p>I covered the full setup process in a video walkthrough. If you want to see the hook in action, including the database creation and a parallel test run, <a href="https://youtu.be/hEE0mc-3D_c">watch it here</a>.</p>
<p>If you&rsquo;re looking to set up isolated agent workflows for your team or need help integrating Claude Code into your development process, <a href="https://www.damiangalarza.com/claude-code">let&rsquo;s talk</a>.</p>
]]></content:encoded></item></channel></rss>