A CLI tool that gives each git worktree its own Postgres database, managed Redis container, ports, and .env files. Prevents worktrees from corrupting each other's data.
When you use git worktree add for parallel development, all worktrees share the same database, Redis instance, and ports. This means:
- Schema migrations in one worktree break another
- BullMQ/Redis queues collide across worktrees
- Two dev servers can't run simultaneously on the same port
.envfiles point to the same resources everywhere
wt solves this by assigning each worktree an isolated slot that determines its database name, Redis container, and port range.
Each worktree gets a numbered slot. The slot determines everything:
| Resource | Formula | Slot 0 (main) | Slot 1 | Slot 2 | Slot 3 |
|---|---|---|---|---|---|
| Database | {baseName}_wt{slot} |
mydb |
mydb_wt1 |
mydb_wt2 |
mydb_wt3 |
| Redis | wt-<repo>-<hash>-slot-<slot>-redis on 6379 + slot * stride |
shared/local | 6479 |
6579 |
6679 |
| Ports | slot * stride + defaultPort |
3000, 3001 | 3100, 3101 | 3200, 3201 | 3300, 3301 |
- Database: Created via
CREATE DATABASE ... TEMPLATE(fast filesystem copy, not dump/restore) - Redis: Runs in a dedicated Docker container per worktree, visible in Docker Desktop
- Ports: Offset by
portStride(default 100) per slot - Env files: Copied from main worktree and patched with the slot's values
Global (available in all repos):
pnpm add -g @tokenbooks/wtPer-project (recommended for teams — version-locked in package.json):
pnpm add -D @tokenbooks/wtCreate this file in your repository root and commit it. See Configuration Reference for full details.
{
"baseDatabaseName": "myapp",
"baseWorktreePath": ".worktrees",
"portStride": 100,
"maxSlots": 50,
"services": [
{ "name": "web", "defaultPort": 3000 },
{ "name": "api", "defaultPort": 4000 },
{ "name": "redis", "defaultPort": 6379 }
],
"envFiles": [
{
"source": ".env",
"patches": [
{ "var": "DATABASE_URL", "type": "database" }
]
},
{
"source": "backend/.env",
"patches": [
{ "var": "DATABASE_URL", "type": "database" },
{ "var": "REDIS_URL", "type": "redis", "service": "redis" },
{ "var": "PORT", "type": "port", "service": "api" }
]
},
{
"source": "frontend/.env",
"patches": [
{ "var": "PORT", "type": "port", "service": "web" },
{ "var": "API_URL", "type": "url", "service": "api" }
]
}
],
"postSetup": ["npm install"],
"autoInstall": true
}echo ".worktree-registry.json" >> .gitignore# Create a worktree with full isolation
wt new feat/my-feature
# Jump into a worktree by slot or branch
cd $(wt open 1)
cd $(wt open feat/my-feature)
# List all worktree allocations
wt list
# Check health
wt doctor
# Clean up by path or slot
wt remove .worktrees/feat-my-feature
# Prune worktrees Git already considers stale
wt pruneThe package ships with a /wt skill for Claude Code. To enable it, symlink from your project:
mkdir -p .claude/skills
ln -s ../../node_modules/@tokenbooks/wt/skills/wt/SKILL.md .claude/skills/wt.mdThen use /wt init, /wt new feat/foo, /wt doctor, etc. inside Claude Code.
Creates a new git worktree and sets up its isolated environment:
- Allocates the next available slot (or uses
--slot N) - Runs
git worktree add .worktrees/<slug> -b <branch> - Creates a new Postgres database from the main DB as template
- Copies all configured
.envfiles, patching each with slot-specific values - Starts a managed Redis Docker container if Redis patching is configured
- Runs
postSetupcommands (unless--no-install)
Opens an existing worktree or creates one on the fly. Prints the worktree path to stdout for easy shell integration:
cd $(wt open 1) # by slot number
cd $(wt open feat/my-feature) # by branch name (creates if not found)- Slot number: Looks up the allocation and prints its path. Exits 1 if the slot is empty.
- Branch name: Scans allocations for a matching branch. If not found, creates a new worktree (like
wt new).
Shell helper tip:
wto() { cd "$(wt open "$@")"; }Sets up an existing worktree that was created manually or by another tool. Useful when:
- You ran
git worktree adddirectly - A worktree's env files need regenerating
- Called automatically by the
post-checkouthook
If the worktree already has a slot allocation, it reuses it.
Removes a worktree and cleans up its resources:
- Drops the worktree's Postgres database (unless
--keep-db) - Removes the managed Redis Docker container for that slot
- Runs
git worktree remove - Removes the allocation from the registry
Accepts either paths (.worktrees/feat-my-feature) or slot numbers (3), not branch names, including batch formats:
wt remove 1 2wt remove 1,2wt remove "1, 2"wt remove --all
Finds worktrees that Git already marks as prunable, then:
- Cleans up
wt-managed resources for matching registry entries - Drops their databases unless
--keep-dbis set - Removes managed Redis containers if present
- Runs
git worktree prune
This is mainly for worktrees that were deleted manually from disk instead of through wt remove.
Use --dry-run to preview what would be pruned.
Shows all worktree allocations with their slot, branch, database, Redis info, ports, and status (ok/stale).
Runs diagnostics:
- Stale entries: Registry points to a path that no longer exists
- Missing databases: Allocated DB doesn't exist in Postgres
- Missing env files: Expected env files not found in worktree
- Orphaned databases:
{baseName}_wt*databases not in the registry
Use --fix to auto-repair stale entries and drop orphaned databases.
All commands support --json for machine-readable output:
{
"success": true,
"data": { ... }
}Or on error:
{
"success": false,
"error": {
"code": "NO_SLOTS",
"message": "All 50 slots are occupied or blocked by ports already in use."
}
}This file lives in your repository root and is committed to version control.
{
// Required: name of your main Postgres database
"baseDatabaseName": string,
// Directory for worktrees, relative to repo root (default: ".worktrees")
"baseWorktreePath": string,
// Port offset per slot (default: 100)
"portStride": number,
// Maximum number of concurrent worktrees (default: 50)
"maxSlots": number,
// Services that need port allocation.
// If you use a `redis` patch and omit a redis service,
// wt assumes { name: "redis", defaultPort: 6379 }.
"services": [
{ "name": string, "defaultPort": number }
],
// Env files to copy and patch for each worktree
"envFiles": [
{
"source": string, // Path relative to repo root
"patches": [
{
"var": string, // Env var name to patch
"type": string, // "database" | "redis" | "port" | "url"
"service": string // Required for "redis", "port", and "url" types
}
]
}
],
// Commands to run in the worktree after env setup (default: [])
"postSetup": string[],
// Whether to run postSetup automatically (default: true)
"autoInstall": boolean
}Legacy configs that used a redis patch without an explicit redis service are auto-migrated on first run.
| Type | What it patches | Input | Output (slot 3) |
|---|---|---|---|
database |
Replaces DB name in a Postgres URL | postgresql://u:p@host:5432/myapp?schema=public |
postgresql://u:p@host:5432/myapp_wt3?schema=public |
redis |
Rewrites a Redis URL to the managed local Redis container on DB 0 | redis://:pass@host:6379/0 |
redis://:pass@127.0.0.1:6679/0 |
port |
Replaces the entire value with the allocated port | 4000 |
4300 |
url |
Replaces the port number inside a URL | http://localhost:4000/api |
http://localhost:4300/api |
The redis, port, and url types require a service field that matches a name in services.
Auto-managed file at the repo root. Add to .gitignore — it's machine-local.
{
"version": 1,
"allocations": {
"1": {
"worktreePath": "/absolute/path/to/.worktrees/feat-auth",
"branchName": "feat/auth",
"dbName": "myapp_wt1",
"redisContainerName": "wt-myapp-a1b2c3d4-slot-1-redis",
"ports": { "web": 3100, "api": 4100, "redis": 6479 },
"createdAt": "2026-02-17T14:30:00Z"
}
}
}Add a post-checkout hook so wt setup runs automatically when switching branches inside a worktree. With Husky:
Create .husky/post-checkout:
#!/bin/bash
prev_head="$1"
new_head="$2"
is_branch="$3"
# Only run on branch checkout (not file checkout)
[ "$is_branch" = "0" ] && exit 0
# Detect if we're in a worktree (not main)
git_common=$(git rev-parse --git-common-dir 2>/dev/null)
git_dir=$(git rev-parse --git-dir 2>/dev/null)
[ "$git_common" = "$git_dir" ] && exit 0
# Resolve main worktree path
main_worktree=$(cd "$git_common/.." && pwd -P)
# Find wt: local install first, then global
wt_bin="$main_worktree/node_modules/.bin/wt"
if [ ! -f "$wt_bin" ]; then
wt_bin=$(command -v wt 2>/dev/null || true)
fi
if [ -z "$wt_bin" ]; then
echo "Warning: wt CLI not found. Install globally (pnpm add -g @tokenbooks/wt) or locally (pnpm add -D @tokenbooks/wt)."
exit 0
fi
echo "Setting up worktree environment..."
"$wt_bin" setup "$(pwd -P)" --json 2>/dev/null && echo "Worktree ready!" || {
echo "Warning: Auto-setup failed. Run 'wt setup' manually."
exit 0
}The hook checks for a local install first (node_modules/.bin/wt), then falls back to a global wt on PATH.
If you are an LLM agent setting up wt for a repository, follow these steps:
Identify these from the repository:
- Database URL format: Search
.envfiles forDATABASE_URL. Extract the database name (the path segment after the port, before?). - Redis URL format: Search for
REDIS_URL.wtwill rewrite it to a local Docker-managed Redis URL on DB 0. - Services and ports: Find all dev server commands and their default ports. Check
package.jsonscripts,docker-compose.yml, and framework configs. - Env files: List all
.envfiles (not.env.example). These are the files that need patching.
For each .env file, identify which variables need patching:
| If the variable contains... | Use patch type |
|---|---|
A Postgres connection URL (postgresql://...) |
database |
A Redis connection URL (redis://...) |
redis + service name (redis) |
Just a port number (3000) |
port + service name |
A URL with a port (http://localhost:3000/...) |
url + service name |
Variables that don't match any pattern (API keys, secrets, feature flags) should NOT be patched.
Using the discovered information, construct the config:
1. baseDatabaseName = the DB name from the main DATABASE_URL
2. services = each dev server as { name, defaultPort }
3. if using a `redis` patch, include { name: "redis", defaultPort: 6379 } unless you want a custom base port
4. envFiles = each .env file with its patches
5. postSetup = the install command for the package manager (npm install, pnpm install, etc.)
Validate that:
- Every
portandurlpatch has aservicethat exists inservices - If using a
redispatch, Docker is available locally and the Redis service port is included or left to the default6379 - The
portStride(default 100) doesn't cause port collisions with other local services maxSlots * portStridedoesn't push ports into reserved ranges (e.g., above 65535)
# Install wt
pnpm add -D @tokenbooks/wt
# Add to .gitignore
echo ".worktree-registry.json" >> .gitignore
# Verify
wt list # Should show "No worktree allocations found."
wt doctor # Should show "All checks passed."
# Smoke test (creates a real worktree + database)
wt new test/wt-smoke --no-install
wt list # Should show the new allocation
wt remove .worktrees/test-wt-smoke
wt list # Should be empty again
# Opt-in Docker integration test for managed Redis
pnpm test:docker{
"scripts": {
"wt": "wt",
"wt:new": "wt new",
"wt:list": "wt list",
"wt:doctor": "wt doctor"
}
}- Node.js >= 20.19.0
- PostgreSQL (running, accessible via
DATABASE_URLin root.env) - Docker (if using
redispatch type) - Git (for worktree operations)
MIT