{"slug": "swarming-claude-code-and-codex-in-parallel-running-multiple-agents-at-once-with", "title": "Swarming Claude Code and Codex in Parallel: Running Multiple Agents at Once with tmux and Git Worktrees", "summary": "A developer built a system to run multiple AI coding agents (Claude Code and Codex) in parallel using tmux and git worktrees. The three-layer protocol uses a plan.json declaration, an orchestrator script that creates isolated worktrees and launches tmux sessions, and worker scripts that execute tasks without interfering with each other. The approach eliminates branch conflicts and enables parallel execution of refactoring, cleanup, and testing tasks.", "body_md": "In my [previous post about a cost budget advisor](https://zenn.dev/bokuwalily/articles/budget-cap-advisor), I built a mechanism that checks how much quota is left before it runs anything. This time I want to take that idea one step further: instead of cycling a single Codex through jobs one after another, **run several of them at the same time** as a swarm.\n\nI'll walk through the actual code for a three-layer protocol where workers declared in `plan.json`\n\nare expanded by `orchestrate-worktrees.js`\n\ninto a tmux session plus git worktrees, so each Codex runs in parallel without interfering with the others.\n\nWhen you run Codex jobs one after another on a single repository, you hit issues like:\n\nSeparating branches with `git worktree`\n\nreduces the conflict risk to zero. Combining that with tmux to launch everything in parallel is the design for this post.\n\n```\nplan.json              ← declaration layer (what goes to which worker)\n      ↓\norchestrate-worktrees.js  ← orchestrator layer (creates worktrees, launches tmux)\n      ↓\norchestrate-codex-worker.sh  ← worker layer (runs Codex, writes artifacts)\n```\n\nThe orchestrator doesn't know about the workers, and the workers don't know about each other. Artifacts are consolidated into three files under `.orchestration/{session}/{worker_slug}/`\n\n.\n\n| File | Role |\n|---|---|\n`task.md` |\nWork instructions for the worker (generated by the orchestrator) |\n`status.md` |\nState: `not started` → `running` → `completed` / `failed`\n|\n`handoff.md` |\nCodex output + git status (written by the worker) |\n\n```\n{\n  \"sessionName\": \"refactor-sprint\",\n  \"repoRoot\": \"~/my-project\",\n  \"worktreeRoot\": \"~/worktrees\",\n  \"coordinationRoot\": \"~/my-project/.orchestration\",\n  \"baseRef\": \"HEAD\",\n  \"replaceExisting\": true,\n  \"launcherCommand\": \"bash ~/.claude/scripts/orchestrate-codex-worker.sh {task_file} {handoff_file} {status_file}\",\n  \"seedPaths\": [\"package.json\", \"tsconfig.json\"],\n  \"workers\": [\n    {\n      \"name\": \"api-types\",\n      \"task\": \"src/api/ のレスポンス型を zod スキーマに移行する。既存のテストが全て通ること。\",\n      \"seedPaths\": [\"src/api/\"]\n    },\n    {\n      \"name\": \"ui-cleanup\",\n      \"task\": \"src/components/ 内の PropTypes を削除し TypeScript 型に一本化する。\",\n      \"seedPaths\": [\"src/components/\"]\n    },\n    {\n      \"name\": \"test-coverage\",\n      \"task\": \"src/utils/ のユニットテストを追加しカバレッジ 80% 以上にする。\",\n      \"seedPaths\": [\"src/utils/\"]\n    }\n  ]\n}\n```\n\n`seedPaths`\n\ncan be specified in two places: globally and per worker. The global `package.json`\n\n/ `tsconfig.json`\n\nare copied into every worker's worktree, while a worker's own `seedPaths`\n\nare dropped only into that worker.\n\nThe entry point has three modes.\n\n```\n# ① dry-run (default): show what would be created, as JSON\nnode scripts/orchestrate-worktrees.js plan.json\n\n# ② --write-only: no worktree creation; only writes task.md / status.md / handoff.md\nnode scripts/orchestrate-worktrees.js plan.json --write-only\n\n# ③ --execute: full run, including worktree creation and tmux launch\nnode scripts/orchestrate-worktrees.js plan.json --execute\n```\n\nWhen you invoke `--execute`\n\n, `executePlan`\n\nfrom `lib/tmux-worktree-orchestrator.js`\n\nruns, processing in this order:\n\n`git rev-parse --is-inside-work-tree`\n\nand `tmux -V`\n\n`replaceExisting: true`\n\n, clean up existing sessions, worktrees, and branches`materializePlan`\n\nwrites the three-file set under `.orchestration/`\n\n`git worktree add -b <branch> <path> <baseRef>`\n\n`tmux new-session -d -s <session>`\n\n`split-window -P -F '#{pane_id}'`\n\n→ set the layout to tiled → set the pane title → send the launch commandBranch names and worktree paths are generated automatically.\n\n```\nbranch:   orchestrator-{session_name}-{worker_slug}\nworktree: {repo_name}-{session_name}-{worker_slug}/   (directly under worktreeRoot)\n```\n\nTo attach after running, all you need is `tmux attach -t refactor-sprint`\n\n. Three panes sit side by side, each running its own Codex.\n\nNote\n\nWith`replaceExisting: false`\n\n(the default), the run stops with an error if a tmux session of the same name already exists. When re-running, either set`replaceExisting: true`\n\nor manually`tmux kill-session -t <name>`\n\nfirst.\n\nThe worker script takes three arguments.\n\n```\nbash ~/.claude/scripts/orchestrate-codex-worker.sh \\\n  <task-file> <handoff-file> <status-file>\n```\n\nYou can wire this straight into `plan.json`\n\n's `launcherCommand`\n\nusing template variables.\n\n```\n\"launcherCommand\": \"bash ~/.claude/scripts/orchestrate-codex-worker.sh {task_file} {handoff_file} {status_file}\"\n```\n\nHere's the spot inside that calls Codex (actual code):\n\n```\ncat > \"$prompt_file\" <<EOF\nYou are one worker in an ECC tmux/worktree swarm.\n\nRules:\n- Work only in the current git worktree.\n- Do not touch sibling worktrees or the parent repo checkout.\n- Complete the task from the task file below.\n- Do not spawn subagents or external agents for this task.\n- Report progress and final results in stdout only.\n- Do not write handoff or status files yourself; the launcher manages those artifacts.\n...\n\nTask file: $task_file\n\n$(cat \"$task_file\")\nEOF\n\nif codex exec -p yolo -m gpt-5.4 --color never -C \"$(pwd)\" -o \"$output_file\" - < \"$prompt_file\"; then\n```\n\nThe key point is `- < \"$prompt_file\"`\n\n, which pipes the prompt into stdin. Because `-C \"$(pwd)\"`\n\npins the execution directory to the worktree, there's no worry about Codex accidentally touching files in the parent repository.\n\nOn success, `handoff.md`\n\nis written with Codex's output and `git status --short`\n\n, and `status.md`\n\nchanges to `completed`\n\n. On failure it becomes `failed`\n\n, with no impact on the next worker.\n\nThe template variables available in `launcherCommand`\n\nare as follows (from the actual source):\n\n| Variable | Contents |\n|---|---|\n`{worker_name}` |\nWorker name (e.g. `api-types` ) |\n`{worker_slug}` |\nSlugified name (e.g. `api-types` ) |\n`{session_name}` |\ntmux session name |\n`{repo_root}` |\nRepository root path |\n`{worktree_path}` |\nThis worker's worktree path |\n`{branch_name}` |\nThis worker's branch name |\n`{task_file}` |\nAbsolute path to task.md |\n`{handoff_file}` |\nAbsolute path to handoff.md |\n`{status_file}` |\nAbsolute path to status.md |\n\nFor each variable, a `_sh`\n\nsuffix version (shell-quoted) and a `_raw`\n\nversion are also generated. When passing a path that contains spaces to another shell script, using `{worktree_path_sh}`\n\nis the safe choice.\n\nThe `renderTemplate`\n\nimplementation is simple: if it contains an undefined variable, it errors out immediately (`Unknown template variable: xxx`\n\n). Typos are caught before execution.\n\nA worktree right after `git worktree add`\n\nis a snapshot as of `baseRef`\n\n, but sometimes you need the latest locally-modified version of `package.json`\n\nor a config file. `seedPaths`\n\nsolves that problem by copying.\n\n```\nfunction overlaySeedPaths({ repoRoot, seedPaths, worktreePath }) {\n  for (const seedPath of normalizedSeedPaths) {\n    const sourcePath = path.join(repoRoot, seedPath);\n    const destinationPath = path.join(worktreePath, seedPath);\n    fs.cpSync(sourcePath, destinationPath, {\n      dereference: false, force: true, preserveTimestamps: true, recursive: true\n    });\n  }\n}\n```\n\nThat said, `seedPaths`\n\ncan't escape outside `repoRoot`\n\n. Paths like `../../../etc/passwd`\n\nare rejected by the `..`\n\ncheck in `normalizeSeedPaths`\n\n.\n\nWarning\n\nFiles copied by`seedPaths`\n\nget committed after they're edited in the worktree. If you seed a global`package.json`\n\nand then rewrite it in the worktree, that change rides along on the branch. To avoid unintended changes, seed only the files you actually need.\n\nEven if `executePlan`\n\nfails midway, it rolls back the resources it was in the middle of creating. It records what has been created so far in `createdState`\n\n, and on error it calls `rollbackCreatedResources`\n\n.\n\n```\n1. kill-session the tmux session\n2. git worktree remove --force each worktree\n3. git worktree prune --expire now\n4. git branch -D the corresponding branches\n5. delete coordinationDir if it didn't originally exist\n```\n\nThis order is the reverse of creation (delete the most recently created first). If one of the rollbacks fails partway through, it continues with the rest and throws a consolidated error at the end.\n\n`replaceExisting`\n\nstill `false`\n\nand getting stuck`true`\n\nat first, or manually kill it while developing.`_sh`\n\nsuffix versions like `{task_file_sh}`\n\n.`--write-only`\n\nbefore `--execute`\n\n.`codex exec`\n\n`executePlan`\n\nchecks for existence by running `tmux -V`\n\nat the top. If it's missing, it errors immediately (with a clear message).`workers`\n\narray in `plan.json`\n\n, and a single `--execute`\n\nlaunches worktree + tmux + Codex all at once`task.md`\n\n/ `status.md`\n\n/ `handoff.md`\n\n, so Claude Code can review them together afterward`launcherCommand`\n\ncan be freely wired with template variables and swapped out for workers other than CodexNext time I'll write about the orchestrator loop where Claude Code reviews all the `handoff.md`\n\nfiles each worker wrote and decides whether they can be merged.\n\n*Written by **Lily** — I ship iOS apps and automate my content stack with Claude Code.\n\nFollow along: [Portfolio](https://bokuwalily.com) · [X](https://x.com/bokuwalily) · [GitHub](https://github.com/bokuwalily)*", "url": "https://wpnews.pro/news/swarming-claude-code-and-codex-in-parallel-running-multiple-agents-at-once-with", "canonical_source": "https://dev.to/bokuwalily/swarming-claude-code-and-codex-in-parallel-running-multiple-agents-at-once-with-tmux-and-git-1fej", "published_at": "2026-07-18 01:00:55+00:00", "updated_at": "2026-07-18 01:27:55.710218+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "large-language-models"], "entities": ["Claude Code", "Codex", "tmux", "git worktrees"], "alternates": {"html": "https://wpnews.pro/news/swarming-claude-code-and-codex-in-parallel-running-multiple-agents-at-once-with", "markdown": "https://wpnews.pro/news/swarming-claude-code-and-codex-in-parallel-running-multiple-agents-at-once-with.md", "text": "https://wpnews.pro/news/swarming-claude-code-and-codex-in-parallel-running-multiple-agents-at-once-with.txt", "jsonld": "https://wpnews.pro/news/swarming-claude-code-and-codex-in-parallel-running-multiple-agents-at-once-with.jsonld"}}