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

> Source: <https://dev.to/croustibat44/running-parallel-claude-code-agents-on-laravel-with-orca-my-setup-and-archive-scripts-3ijn>
> Published: 2026-07-28 09:34:12+00:00

David Hemphill wrote a great post about [getting started with Conductor using Laravel](https://davidhemphill.com/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](https://herd.laravel.com/), 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"

# copy git-ignored MCP config so the agent has its tools
[ -f "$main_root/.mcp.json" ] && cp "$main_root/.mcp.json" .mcp.json

herd composer install --no-interaction --prefer-dist --optimize-autoloader
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`

.

``` bash
#!/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

# same site name derivation as setup
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}"
# (same 58-char truncation as setup)

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:

``` bash
# scripts/worktree-setup.sh
#!/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](https://davidhemphill.com/getting-started-with-conductor-using-laravel); 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.*
