DEV Community

Rohan Sharma
Rohan Sharma Subscriber

Posted on

I got tired of writing the same 80-line setup script for every AI worktree

If you've been running parallel AI coding agents — Claude Code, Aider, Codex, anything — you've hit this wall.

Each agent needs its own worktree. Each worktree needs its own environment. And that means every time you spin one up, you're either manually editing .env files or you've got a setup script that looks something like this:

#!/bin/bash
# conductor.json worktree script — set up isolated environment
BRANCH_NAME="${CONDUCTOR_BRANCH_NAME}"
SLUG="${BRANCH_NAME//\//_}"
SLUG="${SLUG//-/_}"

BASE_PORT=3000
USED_PORTS=$(cat ~/.worktree-ports 2>/dev/null || echo "")
PORT=$BASE_PORT
while echo "$USED_PORTS" | grep -q "^$PORT$"; do
  PORT=$((PORT + 1))
done
echo "$PORT" >> ~/.worktree-ports

DB_NAME="myapp_${SLUG}"
COMPOSE_PROJECT="myapp_${SLUG}"
REDIS_PORT=$((PORT + 1000))

cat > .env.local << EOF
PORT=$PORT
DB_NAME=$DB_NAME
DATABASE_URL=postgres://localhost/$DB_NAME
COMPOSE_PROJECT_NAME=$COMPOSE_PROJECT
REDIS_URL=redis://localhost:$REDIS_PORT
EOF

createdb "$DB_NAME" 2>/dev/null || true
echo "Set up: PORT=$PORT DB=$DB_NAME"
Enter fullscreen mode Exit fullscreen mode

That's 30 lines of bash, no error handling, no cleanup when you tear the worktree down, and it breaks the moment two worktrees race to write to ~/.worktree-ports at the same time.

I've seen this pattern everywhere. The Phoenix community wrote up their version. The folks at 10play wrote theirs. Everyone reinvents the same wheel.

What I built instead
I maintain workz — a Rust CLI for Git worktrees that handles zero-config dep syncing (symlinks node_modules, target, .venv automatically) and AI agent launching. Last week I shipped --isolated:

workz start feature/auth --isolated

Enter fullscreen mode Exit fullscreen mode

Output:

creating worktree for branch 'feature/auth'
worktree created at /home/you/myapp--feature-auth
symlinked node_modules
isolated environment:
PORT=3001 → .env.local
DB_NAME=feature_auth
COMPOSE_PROJECT_NAME=feature_auth
REDIS_URL=redis://localhost:4001
ready!
That's it. It:

Picks the next available port starting from 3000 (atomic, no race conditions)
Sanitizes the branch name into a safe slug (feature/auth → feature_auth)
Writes .env.local with PORT, DB_NAME, DATABASE_URL, COMPOSE_PROJECT_NAME, REDIS_URL
Registers the allocation in ~/.config/workz/ports.json so no two worktrees ever collide
And when you're done:

workz done feature/auth --cleanup-db
Releases the port, removes the worktree, optionally drops the database. No orphaned ports. No stale registry entries.

Running 3 isolated agents in parallel
The reason I care about this: I run multiple Claude Code agents in parallel, each on a different task. Before --isolated, they'd all try to bind to port 3000. First one wins, the other two crash on startup.

Now:

workz fleet start \
--task "add OAuth2 login" \
--task "write integration tests" \
--task "refactor database layer" \
--agent claude \
--isolated
Three isolated worktrees, three unique ports, three separate databases, three .env.local files — all spun up in parallel. Each agent gets a fully isolated environment without touching the others.

workz status shows the full picture:


  main                /home/you/myapp [clean]         342K  2h ago
  feature/auth        /home/you/myapp--feature-auth   89M   5m ago  PORT:3001
  fix/tests           /home/you/myapp--fix-tests       91M   5m ago  PORT:3002
  refactor/db         /home/you/myapp--refactor-db     88M   5m ago  PORT:3003
Enter fullscreen mode Exit fullscreen mode

The Linux angle
I built this partly because Conductor.build — the GUI for parallel Claude Code agents that's been getting a lot of attention — is Mac-only. Apple Silicon required. Linux support is "hopefully soon-ish, but not sure."

Every Linux developer who wants to run parallel AI agents has no Conductor. And even on Mac, Conductor's worktree setup is a bash script you write yourself — there's no environment engine built in.

workz is the answer for that gap:

Linux, Mac (Windows planned)
Open source — MIT, single Rust binary, cargo install workz
Zero-config — auto-detects Node/Rust/Python/Go/Java projects, symlinks deps automatically
--isolated — the environment engine Conductor doesn't have
It's not trying to be a GUI. It's terminal-native, which is what Linux developers actually want.

How the port registry works
The implementation is straightforward. A JSON file at ~/.config/workz/ports.json:


{
"base_port": 3000,
"allocations": {
"feature_auth": {
"port": 3001,
"branch": "feature/auth",
"db_name": "feature_auth",
"compose_project": "feature_auth",
"allocated_at": "2026-03-03T20:00:00Z"
}
}
}

On workz start --isolated: scan allocations for the next unused port, write the entry, write .env.local.
On workz done: remove the entry. Atomic reads and writes, no race conditions between concurrent workz start calls.

The branch slug sanitizer handles edge cases: feature/add-auth → feature_add_auth, collapses repeated separators, lowercases everything.

Install

Homebrew

brew tap rohansx/tap
brew install workz

Cargo

cargo install workz
Add shell integration to your .zshrc / .bashrc:

eval "$(workz init zsh)"

This gives you a workz shell function that auto-cds into the new worktree after workz start.

Repo: https://github.com/rohansx/workz

If you're on Mac and already using Conductor — workz works as your setup script. Drop workz sync --isolated into your conductor.json and you get the environment engine without changing your workflow.

Top comments (0)