cd /news/developer-tools/running-parallel-claude-code-agents-… · home topics developer-tools article
[ARTICLE · art-76687] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Running parallel Claude Code agents on Laravel with Orca: my setup and archive scripts

A developer shared scripts for running parallel Claude Code agents on Laravel using Orca, an agent development environment that creates isolated git worktrees. The setup uses SQLite databases and unique local domains per worktree to prevent collisions between agents, with lifecycle hooks that automate environment configuration.

read7 min views1 publishedJul 28, 2026

David Hemphill wrote a great post about getting started with Conductor using Laravel — how to wire up per-worktree setup and archive scripts so each parallel agent gets its own clean environment. I run the same idea, but with ** Orca** instead of Conductor. This is my take, with the two scripts I actually use.

The moment you run more than one coding agent at a time, they start fighting over shared state. Two agents against the same database means one's migrate:fresh --seed

wipes the other's work mid-task. Two Laravel apps on the same local domain, the same queue, the same Reverb port — collisions everywhere.

Git worktrees fix the files part: each branch gets its own directory on disk. But a Laravel app isn't just files. It's a database, a signed local domain, a websocket port, a built frontend. If you don't isolate those too, "parallel" is a lie.

Orca is an agent development environment: it runs Claude Code, Codex, Gemini and other CLI agents in parallel, each in its own real git worktree. One worktree = one branch = one workspace. You review the diff, ship a PR, then archive the worktree in one click.

The part that makes it usable for Laravel is two lifecycle hooks in the repository settings:

Inside those scripts Orca hands you two environment variables I lean on heavily:

ORCA_ROOT_PATH

— the path to the main repo (the "real" checkout, where my root .env

lives).ORCA_WORKSPACE_NAME

— the name of the current workspace/branch.Both of my scripts fall back to plain git if those aren't set, so the exact same file also works when I run it by hand outside Orca.

My whole convention fits in one sentence: inside a worktree, the database is always an isolated SQLite file, never the project's shared database. A local file that gets created with the worktree and deleted with it. No seed collisions between parallel agents, ever.

Local serving is Laravel Herd, one isolated, HTTPS-secured site per worktree. So agent A works on myapp-feature-invoices.test

while agent B is on myapp-fix-webhooks.test

, each on its own PHP version, its own database, its own everything.

This runs on worktree creation. I keep the real thing in ~/.scripts/laravel/worktree-setup.sh

and reference it from each project with a tiny shim (more on that at the end). Here's what matters.

Find the main repo, copy its .env:

if [ -n "${ORCA_ROOT_PATH:-}" ]; then
    main_root="$ORCA_ROOT_PATH"
else
    main_root="$(dirname "$(cd "$(git rev-parse --git-common-dir)" && pwd)")"
fi

if [ -f "$main_root/.env" ]; then
    cp "$main_root/.env" .env
else
    cp .env.example .env
fi
set -a && source .env && set +a

The root .env

holds my real secrets and API keys, so every worktree inherits a working config with zero manual copy-paste. set -a

exports everything so I can use those values later in the script.

Build a unique, cert-safe site name:

workspace="${ORCA_WORKSPACE_NAME:-$(git rev-parse --abbrev-ref HEAD)}"
workspace_slug="$(echo "$workspace" | sed 's#[^a-zA-Z0-9]#-#g' | tr '[:upper:]' '[:lower:]')"
repo="$(basename "$main_root")"
site_name="${repo}-${workspace_slug}"
max_site_len=58
if [ "${#site_name}" -gt "$max_site_len" ]; then
    ws_hash="$(printf '%s' "$workspace" | cksum | awk '{print $1}')"
    keep=$(( max_site_len - ${#ws_hash} - 1 ))
    site_name="$(printf '%s' "$site_name" | cut -c1-"$keep")-${ws_hash}"
fi

That 58-character cap is a gotcha I learned the hard way: the site name becomes the Common Name of the TLS certificate Herd generates, and a CN longer than 64 characters is invalid. Long branch names (feature/some-very-descriptive-thing

) blow past that, so past the limit I truncate and append a short cksum

hash to keep it unique.

Point .env at the isolated SQLite DB and neutralize mail:

sqlite_path="$(pwd)/database/database.sqlite"
set_env "APP_URL"       "https://${site_name}.test"
set_env "DB_CONNECTION" "sqlite"
set_env "DB_DATABASE"   "${sqlite_path}"
set_env "MAIL_MAILER"   "log"

(set_env

is a small helper that updates a key if present or appends it — BSD sed

, since this is macOS.) Mail goes to the log so an agent can't accidentally send real email from a workspace.

A deterministic Reverb port, only if the project uses Reverb:

if [ -f config/reverb.php ] || grep -q '"laravel/reverb"' composer.json 2>/dev/null; then
    hash_val="$(echo -n "$site_name" | cksum | awk '{print $1}')"
    reverb_port=$((8100 + (hash_val % 100)))
    set_env "REVERB_SERVER_PORT" "${reverb_port}"
    set_env "REVERB_PORT"        "${reverb_port}"
    set_env "VITE_REVERB_PORT"   "${reverb_port}"
fi

Two worktrees can't both bind 8080

. Deriving the port from a hash of the site name gives each worktree a stable, collision-resistant port without me tracking anything.

Link the site to Herd, isolate the PHP version, secure it:

php_ver="$(grep -oE '"php"[^,}]*' composer.json 2>/dev/null | grep -oE '8\.[0-9]+' | head -1 || true)"

herd link "$site_name"
if [ -n "$php_ver" ]; then
    herd isolate "$php_ver" --site="$site_name" || true
fi
herd secure "$site_name"
herd restart

The PHP version is read straight from composer.json

, so a legacy project on 8.2 and a new one on 8.4 each get the right runtime automatically.

Fresh database, dependencies, and a built frontend:

mkdir -p "$(dirname "$sqlite_path")"
rm -f "$sqlite_path"
touch "$sqlite_path"

[ -f "$main_root/.mcp.json" ] && cp "$main_root/.mcp.json" .mcp.json

herd composer install --no-interaction --prefer-dist --optimize-auto
php artisan config:clear
php artisan key:generate --force --no-interaction
php artisan migrate:fresh --seed --force
php artisan storage:link 2>/dev/null || true

if [ -f yarn.lock ]; then
    yarn && yarn build
elif [ -f pnpm-lock.yaml ]; then
    pnpm install && pnpm build
elif [ -f package.json ]; then
    npm install && npm run build
fi

A couple of details I care about: I copy the git-ignored .mcp.json

from the root so the agent inherits my MCP servers, and I detect the frontend package manager from the lockfile instead of hardcoding npm

. I also gate private Composer auth (Flux Pro) behind "is it actually a dependency and are the creds present" so the script stays generic across projects.

By the time this finishes, the agent opens onto a fully working https://myapp-feature-x.test

with its own seeded database. Zero manual setup.

This runs before Orca destroys the worktree. Its whole job is to undo what setup did — and to never block the deletion, so every command is best-effort with || true

.

#!/usr/bin/env bash
set -uo pipefail

if [ -n "${ORCA_ROOT_PATH:-}" ]; then
    main_root="$ORCA_ROOT_PATH"
else
    main_root="$(dirname "$(cd "$(git rev-parse --git-common-dir)" && pwd)")"
fi

workspace="${ORCA_WORKSPACE_NAME:-$(git rev-parse --abbrev-ref HEAD)}"
workspace_slug="$(echo "$workspace" | sed 's#[^a-zA-Z0-9]#-#g' | tr '[:upper:]' '[:lower:]')"
repo="$(basename "$main_root")"
site_name="${repo}-${workspace_slug}"

if [ -z "$workspace_slug" ]; then
    echo "⚠️  Empty workspace name, cleanup aborted for safety."
    exit 0
fi

herd unsecure "$site_name"  || true
herd unisolate "$site_name" || true
herd unlink "$site_name"    || true
rm -f "$(pwd)/database/database.sqlite" || true
herd restart || true

It rebuilds the exact same site_name

with identical logic, then tears the Herd site down (unsecure → unisolate → unlink), deletes the SQLite file, and restarts Herd to purge the config. The empty-name guard matters: if the branch name ever resolves to nothing, I'd rather abort than run herd unlink ""

and nuke something I didn't mean to.

I don't paste these scripts into every project. The source of truth lives once in ~/.scripts/laravel/

, and each repo carries a two-line shim:

#!/usr/bin/env bash
exec "$HOME/.scripts/laravel/worktree-setup.sh" "$@"

In Orca's repository settings I point the Setup Script at scripts/worktree-setup.sh

and the Archive Script at scripts/worktree-archive.sh

. Improve the shared script once and every project — and every future worktree — gets the fix. The scripts auto-detect PHP version, frontend manager, Reverb and Flux, so the same file really does work everywhere.

If you're running agents in parallel on Laravel, the isolation is the whole game: one SQLite file and one Herd site per worktree, created on setup and torn down on archive. Once that's in place, spinning up a third or fourth agent costs nothing, and none of them can corrupt each other's state.

Credit where it's due — the pattern is straight out of David Hemphill's Conductor post; I just adapted it to Orca and my stack. If you've got a sharper version of any of these scripts, I'd genuinely like to see it.

Baptiste — freelance web dev for 20 years, building in Laravel/PHP and shipping small open-source tools.

── more in #developer-tools 4 stories · sorted by recency
── more on @orca 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/running-parallel-cla…] indexed:0 read:7min 2026-07-28 ·