{"slug": "running-parallel-claude-code-agents-on-laravel-with-orca-my-setup-and-archive", "title": "Running parallel Claude Code agents on Laravel with Orca: my setup and archive scripts", "summary": "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.", "body_md": "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.\n\nThe 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`\n\nwipes the other's work mid-task. Two Laravel apps on the same local domain, the same queue, the same Reverb port — collisions everywhere.\n\nGit 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.\n\nOrca 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.\n\nThe part that makes it usable for Laravel is two lifecycle hooks in the repository settings:\n\nInside those scripts Orca hands you two environment variables I lean on heavily:\n\n`ORCA_ROOT_PATH`\n\n— the path to the main repo (the \"real\" checkout, where my root `.env`\n\nlives).`ORCA_WORKSPACE_NAME`\n\n— 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.\n\nMy 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.\n\nLocal serving is [Laravel Herd](https://herd.laravel.com/), one isolated, HTTPS-secured site per worktree. So agent A works on `myapp-feature-invoices.test`\n\nwhile agent B is on `myapp-fix-webhooks.test`\n\n, each on its own PHP version, its own database, its own everything.\n\nThis runs on worktree creation. I keep the real thing in `~/.scripts/laravel/worktree-setup.sh`\n\nand reference it from each project with a tiny shim (more on that at the end). Here's what matters.\n\n**Find the main repo, copy its .env:**\n\n```\nif [ -n \"${ORCA_ROOT_PATH:-}\" ]; then\n    main_root=\"$ORCA_ROOT_PATH\"\nelse\n    main_root=\"$(dirname \"$(cd \"$(git rev-parse --git-common-dir)\" && pwd)\")\"\nfi\n\nif [ -f \"$main_root/.env\" ]; then\n    cp \"$main_root/.env\" .env\nelse\n    cp .env.example .env\nfi\nset -a && source .env && set +a\n```\n\nThe root `.env`\n\nholds my real secrets and API keys, so every worktree inherits a working config with zero manual copy-paste. `set -a`\n\nexports everything so I can use those values later in the script.\n\n**Build a unique, cert-safe site name:**\n\n```\nworkspace=\"${ORCA_WORKSPACE_NAME:-$(git rev-parse --abbrev-ref HEAD)}\"\nworkspace_slug=\"$(echo \"$workspace\" | sed 's#[^a-zA-Z0-9]#-#g' | tr '[:upper:]' '[:lower:]')\"\nrepo=\"$(basename \"$main_root\")\"\nsite_name=\"${repo}-${workspace_slug}\"\nmax_site_len=58\nif [ \"${#site_name}\" -gt \"$max_site_len\" ]; then\n    ws_hash=\"$(printf '%s' \"$workspace\" | cksum | awk '{print $1}')\"\n    keep=$(( max_site_len - ${#ws_hash} - 1 ))\n    site_name=\"$(printf '%s' \"$site_name\" | cut -c1-\"$keep\")-${ws_hash}\"\nfi\n```\n\nThat 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`\n\n) blow past that, so past the limit I truncate and append a short `cksum`\n\nhash to keep it unique.\n\n**Point .env at the isolated SQLite DB and neutralize mail:**\n\n```\nsqlite_path=\"$(pwd)/database/database.sqlite\"\nset_env \"APP_URL\"       \"https://${site_name}.test\"\nset_env \"DB_CONNECTION\" \"sqlite\"\nset_env \"DB_DATABASE\"   \"${sqlite_path}\"\nset_env \"MAIL_MAILER\"   \"log\"\n```\n\n(`set_env`\n\nis a small helper that updates a key if present or appends it — BSD `sed`\n\n, since this is macOS.) Mail goes to the log so an agent can't accidentally send real email from a workspace.\n\n**A deterministic Reverb port, only if the project uses Reverb:**\n\n```\nif [ -f config/reverb.php ] || grep -q '\"laravel/reverb\"' composer.json 2>/dev/null; then\n    hash_val=\"$(echo -n \"$site_name\" | cksum | awk '{print $1}')\"\n    reverb_port=$((8100 + (hash_val % 100)))\n    set_env \"REVERB_SERVER_PORT\" \"${reverb_port}\"\n    set_env \"REVERB_PORT\"        \"${reverb_port}\"\n    set_env \"VITE_REVERB_PORT\"   \"${reverb_port}\"\nfi\n```\n\nTwo worktrees can't both bind `8080`\n\n. Deriving the port from a hash of the site name gives each worktree a stable, collision-resistant port without me tracking anything.\n\n**Link the site to Herd, isolate the PHP version, secure it:**\n\n```\nphp_ver=\"$(grep -oE '\"php\"[^,}]*' composer.json 2>/dev/null | grep -oE '8\\.[0-9]+' | head -1 || true)\"\n\nherd link \"$site_name\"\nif [ -n \"$php_ver\" ]; then\n    herd isolate \"$php_ver\" --site=\"$site_name\" || true\nfi\nherd secure \"$site_name\"\nherd restart\n```\n\nThe PHP version is read straight from `composer.json`\n\n, so a legacy project on 8.2 and a new one on 8.4 each get the right runtime automatically.\n\n**Fresh database, dependencies, and a built frontend:**\n\n```\nmkdir -p \"$(dirname \"$sqlite_path\")\"\nrm -f \"$sqlite_path\"\ntouch \"$sqlite_path\"\n\n# copy git-ignored MCP config so the agent has its tools\n[ -f \"$main_root/.mcp.json\" ] && cp \"$main_root/.mcp.json\" .mcp.json\n\nherd composer install --no-interaction --prefer-dist --optimize-autoloader\nphp artisan config:clear\nphp artisan key:generate --force --no-interaction\nphp artisan migrate:fresh --seed --force\nphp artisan storage:link 2>/dev/null || true\n\nif [ -f yarn.lock ]; then\n    yarn && yarn build\nelif [ -f pnpm-lock.yaml ]; then\n    pnpm install && pnpm build\nelif [ -f package.json ]; then\n    npm install && npm run build\nfi\n```\n\nA couple of details I care about: I copy the git-ignored `.mcp.json`\n\nfrom the root so the agent inherits my MCP servers, and I detect the frontend package manager from the lockfile instead of hardcoding `npm`\n\n. 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.\n\nBy the time this finishes, the agent opens onto a fully working `https://myapp-feature-x.test`\n\nwith its own seeded database. Zero manual setup.\n\nThis 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`\n\n.\n\n``` bash\n#!/usr/bin/env bash\nset -uo pipefail\n\nif [ -n \"${ORCA_ROOT_PATH:-}\" ]; then\n    main_root=\"$ORCA_ROOT_PATH\"\nelse\n    main_root=\"$(dirname \"$(cd \"$(git rev-parse --git-common-dir)\" && pwd)\")\"\nfi\n\n# same site name derivation as setup\nworkspace=\"${ORCA_WORKSPACE_NAME:-$(git rev-parse --abbrev-ref HEAD)}\"\nworkspace_slug=\"$(echo \"$workspace\" | sed 's#[^a-zA-Z0-9]#-#g' | tr '[:upper:]' '[:lower:]')\"\nrepo=\"$(basename \"$main_root\")\"\nsite_name=\"${repo}-${workspace_slug}\"\n# (same 58-char truncation as setup)\n\nif [ -z \"$workspace_slug\" ]; then\n    echo \"⚠️  Empty workspace name, cleanup aborted for safety.\"\n    exit 0\nfi\n\nherd unsecure \"$site_name\"  || true\nherd unisolate \"$site_name\" || true\nherd unlink \"$site_name\"    || true\nrm -f \"$(pwd)/database/database.sqlite\" || true\nherd restart || true\n```\n\nIt rebuilds the exact same `site_name`\n\nwith 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 \"\"`\n\nand nuke something I didn't mean to.\n\nI don't paste these scripts into every project. The source of truth lives once in `~/.scripts/laravel/`\n\n, and each repo carries a two-line shim:\n\n``` bash\n# scripts/worktree-setup.sh\n#!/usr/bin/env bash\nexec \"$HOME/.scripts/laravel/worktree-setup.sh\" \"$@\"\n```\n\nIn Orca's repository settings I point the Setup Script at `scripts/worktree-setup.sh`\n\nand the Archive Script at `scripts/worktree-archive.sh`\n\n. 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.\n\nIf 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.\n\nCredit 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.\n\n*Baptiste — freelance web dev for 20 years, building in Laravel/PHP and shipping small open-source tools.*", "url": "https://wpnews.pro/news/running-parallel-claude-code-agents-on-laravel-with-orca-my-setup-and-archive", "canonical_source": "https://dev.to/croustibat44/running-parallel-claude-code-agents-on-laravel-with-orca-my-setup-and-archive-scripts-3ijn", "published_at": "2026-07-28 09:34:12+00:00", "updated_at": "2026-07-28 09:36:19.005050+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "ai-agents"], "entities": ["Orca", "Claude Code", "Laravel", "Git", "SQLite", "Laravel Herd"], "alternates": {"html": "https://wpnews.pro/news/running-parallel-claude-code-agents-on-laravel-with-orca-my-setup-and-archive", "markdown": "https://wpnews.pro/news/running-parallel-claude-code-agents-on-laravel-with-orca-my-setup-and-archive.md", "text": "https://wpnews.pro/news/running-parallel-claude-code-agents-on-laravel-with-orca-my-setup-and-archive.txt", "jsonld": "https://wpnews.pro/news/running-parallel-claude-code-agents-on-laravel-with-orca-my-setup-and-archive.jsonld"}}