name: ywc-parallel-executor description: >- (ywc) Use when multiple independent tasks from tasks/ can run simultaneously and the user wants faster execution via Git Worktree isolation. Triggers: "병렬 실행", "parallel execute", "parallel-executor", "agent executor", "동시 실행", "워크트리 실행", "execute tasks in parallel", "run tasks simultaneously", "並列実行", "並列ワークフロー". Do not use for strictly sequential tasks (use ywc-sequential-executor), single-task execution, or when no dependency-graph.md exists.
ywc-parallel-executor (Parallel Executor)
Announce at start: "I'm using the ywc-parallel-executor skill to run independent tasks concurrently in isolated worktrees."
Worktree lifecycle is owned by the dedicated
ywc-worktreesskill (priority resolution +--mode create/audit/prune/resolve). This skill delegates worktree creation, audit, and cleanup toywc-worktreesinstead of inlining the procedures. Seeywc-worktrees/SKILL.mdfor the four-mode contract and the priority chain (.worktrees/> CLAUDE.mdworktree_root> caller-specified > legacy fallback). The bundledaudit-worktrees.sh/cleanup-worktree.shscripts have moved to that skill.
Executes tasks generated by task-generator in parallel using agents.
Worktree granularity: parallel execution uses one worktree per task; ywc-sequential-executor --worktree uses one run-level worktree because only one task is active at a time. Do not reuse the sequential model here because it collapses wave isolation and per-task recovery.
Rationalization Defense
When tempted to skip a step, check this table first:
| Excuse | Reality |
|---|---|
| "Tasks look independent, run all in parallel" | Run only tasks whose Ownership and Shared Surfaces do not overlap. Shared Surfaces include public contract names, not just file paths. Dependency-graph.md is the source of truth. |
| "Wave 1 had a partial failure, start Wave 2 anyway" | Wave boundary is a hard gate. All Wave N tasks must succeed before Wave N+1 starts. |
| "Worktree cleanup is manual hassle, skip it" | Stale worktrees corrupt subsequent runs. Always cleanup after each task completes via ywc-worktrees --mode prune (see ywc-worktrees). |
| "Conflicts With is just a hint" | If two tasks declare Conflicts With each other, they cannot run in the same wave even if dependencies allow. |
| "Task category does not match any specialized agent, skip the assignment" | Default to executor agent. Never silently drop the task. |
| "Mid-wave one task is slow, kill it and continue" | A killed task leaves a dirty branch. Stop, report, and let the user decide. |
| "--draft creates PR at the end, bot review can wait until un-draft" | Bot review bots (CodeRabbit, Codex Review, Claude Review) post on draft PRs immediately. After creating the draft PR in Step 5, poll for reviews via ../references/pr-bot-polling.md and invoke ywc-handle-pr-reviews as a PR health sweep regardless of BOT_COUNT == 0; the handler also checks CI status and merge-readiness. The PR stays draft — responding now avoids a round-trip after un-drafting. |
| "The aggregate draft PR passed CI before bot reviews — re-checking CI after bot fixes is extra work" | Bot review fixes push new commits that trigger a fresh CI run. The pre-bot CI result is stale and does not cover the updated code. Always re-verify CI after pushing bot review fixes on the aggregate draft PR (Step 5). |
| "Last task in the last wave has no downstream wave, skip delivery for it" | Step 4e is unconditional — every task in every wave, including the last task in the last wave, must complete (a) and (b). The absence of a downstream wave is not a reason to skip delivery. Without delivery (ywc-finish-branch for --local-merge/--draft, or gh pr merge + inline Mark Complete for --per-task-pr), the implementation code is stranded on an orphaned feature branch, tasks/completed/ is wrong, and the base branch is missing the work. |
"All waves done in --draft mode — go straight to Completion Report" |
For --draft, before the Completion Report the aggregate draft branch must be created and pushed (Step 5 pre-report). The run does not end at the last wave's 4i; the Step 5 pre-report steps are required. |
"--per-task-pr: the PR is created, move to the next task" |
--per-task-pr is now a full-lifecycle mode. Creating the PR is not delivery — you must wait for CI to pass, handle bot reviews, merge the PR (gh pr merge --delete-branch), sync local base, and commit + push the completion marker, all within the task's slot in the wave (Step 4e (a)+(b)). A created-but-unmerged PR is an incomplete task. |
"--per-task-pr: CI is green, merge the PR" |
A sibling task in the same wave may have merged first, leaving this PR CONFLICTING or BEHIND. Refresh the branch against the latest base (Step 4e (a) step 4, merge-not-rebase per ../references/pr-conflict-resolution.md) and re-verify CI before gh pr merge. A real textual conflict on refresh marks the task BLOCKED — never force-merge. |
"--aggregate-pr: the single PR is created, the group is done" |
--aggregate-pr is full-lifecycle, not draft. Creating the aggregate PR is not delivery — you must mark it ready, wait for CI, handle bot reviews, pass the merge-readiness gate, merge it (gh pr merge --delete-branch), and sync local base. A created-but-unmerged aggregate PR is an incomplete group. See references/aggregate-pr.md §B. |
"--aggregate-pr: re-Mark-Complete the tasks after the PR merges" |
Each task's chore: mark ... as completed commit already landed on the aggregate branch during its wave (--defer-push local merge, same as --draft). The aggregate PR merge carries those marker commits into base. Running Mark Complete again would double-move directories. Do not re-mark. |
"Run several --aggregate-pr groups in parallel in this one clone" |
They share the local <base> branch ref, which the --draft-style accumulation mutates (git reset --hard origin/<base> + per-task merges) — concurrent groups corrupt each other's base accumulation. Parallel groups need one clone per group (or run them back-to-back). See references/aggregate-pr.md §C. |
| "Use git worktrees to split the parallel groups inside one clone" | Worktrees separate the working tree and the untracked .ywc-run-state.json, but share .git refs — the local <base> branch is shared and can be checked out in only one worktree, so the accumulation still collides at the ref layer. Worktrees are not an isolation boundary for this mode; use separate clones. See references/aggregate-pr.md §C. |
| "Each wave has one task, so work directly on the aggregate branch" | A fully linear chain is the wrong input for this skill. Stop at Step 2 and route to ywc-sequential-executor; never invent an aggregate-branch-serial path. |
"--aggregate-pr means commit tasks on the aggregate branch" |
--aggregate-pr changes only end-of-run delivery. Per-task execution still requires isolated worktree + feature branch + local merge; the aggregate branch is created in Step 5, never used as a task workspace. |
Violating the letter of these rules is violating the spirit. Parallel execution is faster only when wave isolation is honored.
Arguments
| Parameter | Format | Example | Description |
|---|---|---|---|
| Task specifier | <name> or <start>..<end> |
000001-010..000002-040 |
Single task or range. Both 001010 (legacy) and 000001-010 (new 6-digit PHASE) formats are accepted; range matching uses lexical order. |
--all |
flag | Execute all tasks | |
--tasks-dir |
--tasks-dir <path> |
--tasks-dir tasks/ |
Tasks directory (default: tasks/) |
--review |
flag | Auto-run ywc-impl-review after each task | |
--local-merge |
flag | No PR, merge and push to base-branch directly | |
--draft |
flag | Create draft PR after all tasks complete | |
--per-task-pr |
flag | Per task: create the PR, wait for CI, handle bot reviews, then merge the PR (gh pr merge --delete-branch), sync base, and mark complete — the full lifecycle, mirroring ywc-sequential-executor's default normal-pr mode |
|
--aggregate-pr |
flag | Whole invocation → one branch + one PR. Tasks still run in parallel and accumulate onto a single aggregate branch, then the end-of-run PR is marked ready, CI-verified, bot-reviewed, and merged. The full-lifecycle twin of --draft. See references/aggregate-pr.md |
|
--group-name |
--group-name <name> |
--group-name payments |
Names the aggregate branch (aggregate/<name>) and disambiguates concurrent groups. --aggregate-pr only; defaults to aggregate/<base-branch>-<timestamp> when omitted |
--pr-lang |
--pr-lang <lang> |
--pr-lang ko |
Preferred PR title/body language for --draft and --aggregate-pr; pass through to $ywc-create-pr as --lang <lang> |
--terse |
flag | Compact Completion Report: task table + Completion Status only — no prose reminders, no worktree audit lines, no mode explanations |
--review can be combined with other flags.
Flag conflicts: --local-merge, --draft, --per-task-pr, and --aggregate-pr are mutually exclusive. If multiple are specified, ask for clarification before execution. --group-name is valid only with --aggregate-pr.
Default behavior: When none of --local-merge, --draft, --per-task-pr, or --aggregate-pr is specified, ask the user which mode to use before execution. Do not silently default to any mode. Present: --local-merge (direct merge/push), --draft (single draft PR left open), --per-task-pr (full lifecycle PR per task), or --aggregate-pr (one full-lifecycle PR for the whole invocation).
Definition of Done
A task is done only when all of the following have happened, in this order. A task that is "merged but not marked" is not done — downstream waves resolve dependencies by reading <tasks-dir>/completed/, so a missing move silently breaks the dependency graph for the next wave.
- The feature branch passed Task Verify (Step 4c) and any optional review (Step 4d).
- The task was delivered in Step 4e. For
--local-merge,--draft, and--aggregate-pr,ywc-finish-branchreturnedDONE— that status enforces the merge with post-merge verification and the move of the task directory to<tasks-dir>/completed/<task-name>with achore: mark <task-name> as completedcommit. For--per-task-pr, the PR passed CI, was merged viagh pr merge --delete-branch, local base was synced, and the same completion-marker move-commit was made and pushed. - For
--local-mergeand--per-task-pr: every merge / PR-merge and completion-marker commit was pushed immediately during the wave. For--draftand--aggregate-pr: the deferred local merge commits accumulate locally and are pushed once at the end of all waves before the Completion Report. - The worktree was removed and the feature branch was deleted (Step 4g) — finish-branch leaves the local branch alive when called with
--keep-branch, which is mandatory in this skill because the worktree owns the branch checkout. Step 4g is therefore the source of truth for branch deletion in parallel execution.
If any of the four is missing, the task is incomplete regardless of how git log --oneline base-branch looks. The Wave Delivery + Mark Complete step (Step 4e — ywc-finish-branch for --local-merge/--draft/--aggregate-pr, inline gh pr merge + Mark Complete for --per-task-pr) writes the code and the contract; Step 4g releases the worktree and the branch.
--aggregate-pr group-level done: in addition to every task satisfying the four conditions above, the group is done only when the single aggregate PR has been marked ready, passed CI, cleared bot review, passed the merge-readiness gate, and been merged with local base synced (Step 5 + references/aggregate-pr.md §B). A run whose aggregate PR is created but unmerged is DONE_WITH_CONCERNS at best, never DONE.
Non-Stop Execution Principle
Action required before creating any worktree: Read
../references/non-stop-execution.mdnow. It defines the exhaustive allowed-stop list, the force-continue rule, tool permission prompt handling, and the Tool Error Recovery policy (how to recover fromEdit/Bashfailures without entering extended thinking). Do not defer this read to mid-execution.
Execute all waves without interruption. Do not ask the user for confirmation between waves. The shared rule, full stop list, force-continue rule, zero-output rule, and --local-merge rationale live in ../references/non-stop-execution.md.
The unit for this skill is wave. The Allowed Stop Reasons that are parallel-specific are:
- Pre-flight failure
- Circular dependency detected
- All tasks in a wave failed
git pushrejected by remote- Merge conflict
The "Zero output between transitions" rule applies to the gap between Step 4g/4h of one wave and Step 4a of the next. Everything else — Force Continue Rule, what not to stop for, tool-permission-prompts-are-not-stop-conditions, and the --local-merge rationale — is in the reference and applies verbatim.
Pre-flight
Resume check first: Before running the checks below, look for
.ywc-run-state.jsonin the project root. If it exists, follow the Resume Detection procedure in references/checkpoint-resume.md, including the intent-match guard before offering resume. If the user confirms resume, skip Pre-flight and jump to the saved wave and pending tasks. If the user declines, chooses discard/new run, or there is no state file, proceed with the checks below.
Verify the following conditions before starting:
Clean working tree —
git status --porcelainmust be emptyOn base branch — Must be on main/develop/master
Tasks directory exists —
dependency-graph.mdand task subdirectories must be presentgh CLI authenticated (PR creation modes only) —
gh auth statusmust succeedNo stale worktrees from previous runs — Delegate to
ywc-worktrees --mode auditbefore creating anything. This applies the shared priority chain (.worktrees/> CLAUDE.mdworktree_root> caller-specified root > legacy fallback) and reports stale, leaked, or unexpected worktrees:$ywc-worktrees --mode audit # exit 0 = clean; exit 1 = stale/leaked/unexpected worktrees detected — details on stdoutIf exit 1, resolve per worktree listed in the output:
- The task already merged →
git worktree remove --force <path> && git branch -D feature/<task-name> - The task has unmerged work → stop and surface to the user; do not silently delete in-progress work
Pre-flight must end with exit 0 before Step 1 begins. Starting a new run on top of stale metadata is the most common reason worktrees "leak" — the new run reuses paths and branches the cleanup logic no longer recognises.
- The task already merged →
State Init (non-resume runs only): Initialize .ywc-run-state.json from the computed wave plan, and add it to .gitignore if absent:
grep -qxF '.ywc-run-state.json' .gitignore 2>/dev/null || echo '.ywc-run-state.json' >> .gitignore
STATE_SCRIPT="${CODEX_HOME:-$HOME/.codex}/skills/scripts/update-state.py"
[ -f "$STATE_SCRIPT" ] || STATE_SCRIPT="codex/skills/scripts/update-state.py"
python3 "$STATE_SCRIPT" init-parallel \
--mode <local-merge|draft|per-task-pr|aggregate-pr> --tasks-dir <tasks-dir> \
--waves '[{"wave":1,"tasks":["t-a","t-b"]},{"wave":2,"tasks":["t-c"]}]'
The --waves array is the wave plan from Step 3 — one entry per wave, tasks listing that wave's task-directory names. See the schema in references/checkpoint-resume.md.
Pre-authorizing Tool Permissions (required for multi-wave execution)
The Non-Stop Execution Principle above governs LLM-level pausing, but it cannot override Codex's tool approval layer — every unprompted git/gh/mv command can independently block on user confirmation. In multi-wave execution this is the single most common reason waves stop mid-run: each worktree creation, merge, push, and cleanup across multiple tasks triggers an individual prompt.
Required for multi-wave execution: Each wave runs multiple
gitandghcommands across multiple worktrees. Without pre-authorization, each command pauses for approval — multiplied across all tasks and waves, this makes unattended execution impossible. Complete this setup before starting.
The fix is a one-time Codex command-approval setup that pre-authorizes a small set of read-only and task-scoped command prefixes. The same permissions are used by both ywc-parallel-executor and ywc-sequential-executor.
For the full pattern list, narrow fallback for strict policies, privacy notes, and diagnosis steps when pre-authorization is not enough, see ../references/local-merge-permissions.md.
Checkpoint and Resume
The executor writes .ywc-run-state.json in the project root after each major wave event. If a multi-wave run is interrupted, resume from the last checkpoint: completed waves are skipped and the in-progress wave restarts with only its remaining pending tasks. For the resume procedure, on-disk state schema, checkpoint event table, and manual inspection commands, read references/checkpoint-resume.md.
Execution Steps
Step 1: Parse Dependency Graph
Read dependency-graph.md to extract the TaskInfo list. Parse the following from each task:
- Task ID (Phase + Sequence)
- Task Name (full directory name)
- Category (
db,api,ui,test, etc.) - Depends On (predecessor task list)
- Agent Hint (if specified in README.md)
Step 2: Plan Waves (Topological Sort)
Separate tasks into waves based on dependency relationships:
- Wave 0: Root tasks with no dependencies
- Wave N: Tasks that become executable after all Wave N-1 tasks complete
- If a circular dependency is detected, stop immediately and report
Linear-chain guard: if every planned wave contains exactly one task, stop before worktree creation and route the user to ywc-sequential-executor (with --aggregate-pr when they want single-PR delivery). Proceed in parallel only after explicit user confirmation; never work directly on the base or aggregate branch.
Planning Advisor (optional, Pattern C)
Wave Planning is the critical upfront decision in this skill — a wrong wave boundary cascades into unnecessary serialization (waste) or unsafe parallelism (merge conflicts and broken dependencies) across every subsequent wave. Because the damage is expensive to undo once worktrees and feature branches exist, this is the right place to apply Pattern C from advisor-pattern.md: a single upfront advisor call before worktree creation begins.
When to invoke:
- Task count is 4 or more, AND
- At least one of: any candidate wave contains 3+ concurrent tasks;
Conflicts Withdeclarations exist across the task set;Shared Surfaces/ public contract names overlap across candidate waves; or the first-pass topological sort produced a wave with mixed categories (e.g.,db+api+uiin the same wave). - Skip for ≤3 tasks or purely linear task chains — the topological order is obvious and frontier reasoning adds no value.
How to invoke: Spawn one higher-capability advisor subagent. Payload (≤200 lines total):
- Dependency graph excerpt — tasks +
Depends On+Conflicts With+Shared Surfacesonly; do not forward full task READMEs. - First-pass wave assignment from your topological sort.
- Category distribution per wave (Backend N / Frontend M / QA K / etc).
Ask the advisor for three things:
- Wave boundary confirmation — does the first-pass assignment group tasks safely, or should any task move to a different wave?
- Agent assignment risk — any task whose Category-based agent assignment is a poor fit (for example, a
refactortask that is actually a domain logic rewrite in disguise)? - Conflict detection — any Shared Surfaces the topological sort missed that would cause merge conflicts during Wave Merge (Step 4e)?
Budget: exactly 1 advisor call per invocation. Mid-wave escalation is explicitly disallowed — per-wave task agents run in isolated worktrees and handle their own decisions. If the initial wave plan proves wrong during Step 4, stop execution, report to the user, and re-run the skill with refined input rather than calling another advisor mid-flight.
Advisor output format (≤300 words):
- Wave boundary verdict (confirm, or specific relocations)
- Agent assignment verdict (confirm, or specific reassignments)
- Conflict warnings (if any, with the affected Shared Surface named)
- Single "proceed" or "reconsider with refinements" verdict
After the verdict, either continue to Step 3 with the adjusted wave plan, or surface the "reconsider" verdict to the user for plan refinement before proceeding.
Step 3: Assign Agents by Task Category
| Category | Codex worker role | Description |
|---|---|---|
db, api, domain, lib, worker |
Backend worker | Server-side code generation/modification |
ui |
Frontend worker | UI component, page generation/modification |
test |
QA worker | Test strategy + test code generation |
infra |
DevOps worker | CI/CD, deployment configuration |
refactor |
Reviewer worker | Code structure improvement |
If an Agent Hint is specified in the task's README.md, it overrides the mapping above.
Docker Isolation Audit — After the final wave plan is known and before any worktree creation, audit Docker stacks for every selected task name:
$ywc-docker-isolate --mode audit --expect <comma-separated selected task names>
# exit 0 always; stdout non-empty = residual Docker stacks detected
If stdout is non-empty, abort before Step 4 and surface the printed stacks. Remediation is $ywc-docker-isolate --mode audit --expect <comma-separated selected task names> --prune only after confirming those task-scoped stacks are stale; otherwise they reuse the same deterministic port blocks and will collide during setup.
Step 4: Execute Waves in Parallel
Repeat the following for each wave. Each task must have its own independent worktree and branch — never execute multiple tasks on a single branch. The worktree model structurally guarantees this, but ensure agents do not work outside their assigned worktree.
4a. Create Worktrees — For each task in the wave, delegate creation to ywc-worktrees and capture the resolved path it returns. Do not hardcode ../worktree-*; projects may use .worktrees/, CLAUDE.md worktree_root, or another caller-provided root.
$ywc-worktrees --mode create \
--task-name <task-name> \
--branch feature/<task-name> \
--base-branch <base-branch>
Record the resolved worktree path for the subagent payload. Record the resolved root (worktree_root) and root kind (standard or legacy) in .ywc-run-state.json so resume validation checks the same location used during creation.
4a-verify (mechanical gate — do not skip): before Step 4b, confirm each task's resolved path appears in git worktree list --porcelain and is an existing directory. If any path is missing, re-run Step 4a or stop BLOCKED; never spawn agents and never fall back to the base or aggregate branch working tree.
After 4a-verify passes for a task, set up Docker isolation using the same resolved worktree path returned by ywc-worktrees:
$ywc-docker-isolate --mode setup --task-name <task-name> --worktree-path <resolved-worktree-path>
# exit 0 = PASS or no Docker compose detected; exit 1 = setup failed
If setup exits 1, mark that task BLOCKED, preserve its branch/worktree for recovery, and continue the wave with any other tasks whose setup passed. Do not replace or retry ywc-worktrees --mode create; Docker isolation wraps the created worktree and never owns git lifecycle.
Checkpoint: use the same STATE_SCRIPT resolver above, then run python3 "$STATE_SCRIPT" wave-start <N> — flips wave <N> to in_progress, populates pending from its task list, and stamps last_checkpoint. (Hand-editing the JSON risks malformed state and stale timestamps; the script does the mutation deterministically.)
4b. Spawn Agents — Use Codex subagent delegation to spawn one worker subagent per task in parallel. Pass each subagent:
The task's
task.md(implementation checklist)The task's
README.md(scope, ownership, spec reference)The worktree path (working directory)
The canonical term table from
docs/ubiquitous-language.mdif it exists in the project root (include the "Synonyms to Avoid" column — identifiers matching those entries are naming violations)The shared schema guide when the task touches database schema, migrations, ORM models, relations, indexes, tenant scoping, or enum domains: ../references/schema/core.md plus the stack file matching the project (
prisma.md/sql-ddl.md/drizzle.md/typeorm.md)Question-First directive (append verbatim):
Before any code change: read
task.mdand the Spec Reference, then enumerate genuinely ambiguous decisions whose wrong answer would force a rewrite (interface shape, data model, naming that conflicts with existing code, library choice when more than one is installed). If the list is non-empty, returnNEEDS_CONTEXTwith the questions enumerated — do not infer from neighboring tasks. Inferring silently compounds error and is the most expensive failure mode. See ../references/question-first-gate.md for what counts as genuine ambiguity and the question format.Completeness directive (append verbatim to every subagent prompt):
This implementation will be merged directly into the base branch — treat it as production code. Before returning output: (1) every function/method must have a complete implementation body — no
// TODO, no// rest of code, no placeholder stubs; (2) all imports must be used and all referenced symbols must be defined; (3) tests must contain real assertions, not emptyit()blocks; (4) if token budget is approaching and generation is incomplete, stop at a clean function boundary and write[PAUSED — X of Y files complete. Continue: <file-list>]— never truncate mid-function. A stub is a compile error; a truncated function is worse.Tool Error Recovery directive (append verbatim to every subagent prompt):
When a tool call returns an error, do not enter extended thinking — apply the recovery action immediately. For
Edit/Update→ "Error editing file": (1) re-read the full file withRead, (2) retry the edit withold_stringfrom the fresh content. ForBashnon-zero exit: inspect the error, fix the root cause (wrong flag, path, binary), re-run. Maximum 2 fix attempts for any tool error before returningBLOCKEDwith the file path, attempted change, and exact error text.Simplicity + Surgical Changes directive (append verbatim to every subagent prompt):
Implement the minimum code that satisfies this task — no speculative features, no unsolicited abstractions, no "flexibility" that wasn't asked for. When editing existing code: touch only files listed in your declared Ownership; do not improve adjacent code, comments, or formatting unless they are the direct subject of this task. If you notice unrelated issues, mention them in the PR description — do not fix them. Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify before committing.
Contract + TDD directive (append verbatim): Apply ../references/tdd-deep-module-gray-box.md. Before behavior-changing work, record Changed Public Contracts, Critical Internals, and Cross-Module Impact; write or identify the failing test/contract assertion first; return tests authored/executed plus any
TDD Exception: <reason>.
Handling each subagent's status return: each subagent ends with DONE, DONE_WITH_CONCERNS, BLOCKED, or NEEDS_CONTEXT. The orchestrator's response is defined by ../references/subagent-status-actions.md. In particular: NEEDS_CONTEXT → provide the missing context and re-dispatch the same subagent at the same model class (context is the cheapest fix); BLOCKED → run the four-step triage (context → reasoning → scope → plan) before surfacing to the user; DONE_WITH_CONCERNS → read the concerns and decide whether they are correctness-level (fix and re-dispatch) or observation-level (carry forward to the Completion Report). Do not silently retry the same subagent on the same input — change the input or the model class between attempts.
Status Routing — Apply to every per-task subagent return inside the wave loop:
| Returned status | Caller action |
|---|---|
DONE |
Proceed to Step 4c for this task and continue the wave. |
DONE_WITH_CONCERNS |
Observation concerns go to the Completion Report; correctness concerns require fix and re-dispatch before merge. |
BLOCKED |
Run the four-step triage, preserve the task branch/worktree if unresolved, and apply Wave Failure Handling. |
NEEDS_CONTEXT |
Provide missing context and re-dispatch the same subagent at the same model class. |
| Status absent or unparseable | Treat as BLOCKED; preserve the worktree and surface the raw payload. |
4c. Task Verify — After each agent completes, run the Task Verify commands from task.md. If behavior changed, also run authored/touched contract or behavior tests before broad verification; a worker with no test evidence must be re-dispatched, marked DONE_WITH_CONCERNS, or blocked with a TDD exception.
4d. Review (optional) — If --review is set, auto-invoke ywc-impl-review on the task's worktree branch after Task Verify (4c) and before the Wave Delivery (4e). For --local-merge, --draft, and --aggregate-pr, this is the last quality gate before the change reaches base with no remote bot review yet. For --per-task-pr, a remote bot review also runs after PR creation (Step 4e (a)), so here --review acts as a pre-PR gate that reduces bot round-trips rather than being the only gate.
4e. Wave Delivery + Mark Complete — After all tasks in the wave complete their implementation, deliver successful tasks into the base branch sequentially and mark each one complete. Delivery into the base branch is required for every mode because downstream waves branch from it. For --local-merge, --draft, and --aggregate-pr, the per-task git merge --no-ff + post-merge verification + Mark Task Complete + push (or defer) is delegated to ywc-finish-branch. For --per-task-pr, this skill runs the full PR lifecycle (create → CI → bot → gh pr merge → base sync) and an inline Mark Complete, because the worktree model is incompatible with finish-branch's normal-pr mode (which assumes the feature branch is the current checkout and runs a local git checkout <base> that cannot execute from a worktree).
⚠️ DO NOT SKIP DELIVERY FOR THE LAST TASK IN THE LAST WAVE. There is no exception. Even when there are no remaining waves and no downstream task waiting, Step 4e (a)+(b) must run for every task. For --local-merge, --draft, and --aggregate-pr, ywc-finish-branch performs the local merge, completion-marker commit, and (for local-merge) the push. For --per-task-pr, (a) merges the PR via gh pr merge --delete-branch and (b) commits and pushes the completion marker. Skipping delivery leaves implementation code on an orphaned branch and tasks/completed/ out of sync.
For each task in the wave sequentially (topological order within the wave) — every task in every wave, including the last task in the last wave, must complete steps (a) and (b); no task is exempt because there is no downstream task waiting on it:
(a) Per-task PR lifecycle — applies only to --per-task-pr (runs unconditionally for every task in every wave, including the last task in the last wave). Skip entirely for --local-merge, --draft, and --aggregate-pr. All commands are branch-explicit so they are safe to run from the main checkout while the feature branch lives in its worktree:
- Push the feature branch and create the PR:
git push origin feature/<task-name> gh pr create --base <base-branch> --head feature/<task-name> --title "<task-name>" --body "..." - Wait for CI to pass (up to 2 fix attempts per failing cycle). Poll the PR's checks; on failure, fix on the worktree branch, push, and re-poll:
Categorize failures as lint/format (run the project's auto-fix command first), type, test, or build — fix on the worktree branch, commit,gh pr checks <pr-number> --watchgit push origin feature/<task-name>, then re-poll. After 2 failed fix cycles, mark the taskBLOCKED, preserve its worktree and branch (skip Step 4g for it), and record it for the Completion Report. - Run PR health sweep after automated review polling: run the polling loop in
../references/pr-bot-polling.md, then invokeywc-handle-pr-reviewsfor this task's PR regardless ofBOT_COUNT == 0. A zero bot-comment count is not terminal success; the handler checks review artifacts, CI status, and merge-readiness. If the handler applies fixes, re-verify CI (step 2) — fixes push new commits that trigger a fresh CI run — then re-poll. Repeat until CI is green, merge-readiness is clean, and no new review artifacts arrive within the polling window. PR health sweeps across tasks in the same wave are processed one task at a time so each task's review commits land before its merge. - Refresh the PR branch against the latest base if another task already advanced it — this is the canonical merge-not-rebase procedure in
../references/pr-conflict-resolution.md. This matters when branch protection requires PR branches to be up to date, or when a previous task in the same wave already merged and pushed its completion marker. Run these commands from the resolved worktree path returned in Step 4a:
If the base refresh conflicts, mark the taskgit fetch origin <base-branch> if ! git merge-base --is-ancestor origin/<base-branch> feature/<task-name>; then git -C <resolved-worktree-path> merge --no-ff origin/<base-branch> -m "Merge origin/<base-branch> into feature/<task-name>" git -C <resolved-worktree-path> push origin feature/<task-name> # The branch now has a new commit; return to step 2 and re-verify CI. fiBLOCKED, preserve its worktree and branch, and record the conflict for the Completion Report. Do not force-push and do not merge a PR whose checks correspond to an older head SHA. - Merge the PR once CI is green, bots are quiet, and the branch is current with the latest base:
gh pr merge <pr-number> --merge --delete-branch--delete-branchremoves only the remote branch; the local branch (checked out in the worktree) is deleted in Step 4g. - Sync local base so the next wave branches from the merged state:
The main checkout is already ongit pull origin <base-branch><base-branch>; this fast-forwards it to include the PR merge.
(b) Mark Complete — move the task directory to completed/ and commit the marker. The mechanism differs by mode because --per-task-pr already merged via gh pr merge in (a), while --local-merge, --draft, and --aggregate-pr still need the local merge that ywc-finish-branch performs.
For --local-merge, --draft, and --aggregate-pr — delegate the local merge + Mark Complete to ywc-finish-branch. Mode mapping:
parallel-executor --mode |
finish-branch invocation |
|---|---|
--local-merge |
--mode local-merge --keep-branch (push every task immediately) |
--draft |
--mode local-merge --keep-branch --defer-push (push deferred to end of all waves; a single draft PR is created in Step 5) |
--aggregate-pr |
--mode local-merge --keep-branch --defer-push (identical to --draft per wave; Step 5 lifts the accumulated state onto one branch and runs the full merge lifecycle per references/aggregate-pr.md §B) |
$ywc-finish-branch \
--mode local-merge \
--branch feature/<task-name> \
--base-branch <base-branch> \
--task-name <task-name> \
--tasks-dir <tasks-dir> \
--keep-branch \
[--defer-push only when parallel mode is --draft or --aggregate-pr — controls only the Mark-Complete commit push, never the merge or any earlier step]
--keep-branch is required: the branch is checked out in the resolved per-task worktree, so git branch -d would fail until Step 4g releases the worktree.
For --per-task-pr — the merge already happened in (a) step 5, so do not call finish-branch (its local-merge would attempt a redundant merge, and its normal-pr assumes the feature branch is the current checkout, which it is not under the worktree model). Instead, run the same Mark Complete that finish-branch would, then push immediately:
MARK_SCRIPT="${CODEX_HOME:-$HOME/.codex}/skills/scripts/mark-complete.sh"
[ -f "$MARK_SCRIPT" ] || MARK_SCRIPT="codex/skills/scripts/mark-complete.sh"
bash "$MARK_SCRIPT" <tasks-dir> <task-name> --push
This is the same shared marker script ywc-finish-branch Step 7 uses. It moves the task into completed/, writes the mandatory chore: mark <task-name> as completed commit (handling the gitignored-<tasks-dir> case with a plain mv + --allow-empty commit), pushes the current branch — which under this flow is the synced base — and verifies the move (destination exists, source gone, marker at HEAD). A non-zero exit means the move or verification failed: mark the task BLOCKED, do not treat it as delivered. A --per-task-pr task is delivered only when its PR is merged (a step 5), local base is synced (a step 6), and this marker commit is pushed.
Status return handling for --local-merge, --draft, and --aggregate-pr (per task within the wave loop): ywc-finish-branch ends with DONE, DONE_WITH_CONCERNS, BLOCKED, or NEEDS_CONTEXT. Apply ../references/subagent-status-actions.md:
DONE→ proceed to the next task in the wave.DONE_WITH_CONCERNS→ if observation-level, carry into the Completion Report; if correctness-level, fix and re-dispatch before continuing.BLOCKED→ preserve the task's branch and worktree (Step 4g must skip it for recovery), record for the Completion Report, continue with the remaining wave tasks. Common BLOCKED causes here: merge conflict, post-merge verification mismatch, post-move verification mismatch.NEEDS_CONTEXT→ provide the missing context and re-dispatch.
Status handling for --per-task-pr (no finish-branch delegation): the inline path has three failure gates — a CI failure exhausted after 2 fix cycles (a step 2), a latest-base refresh conflict (a step 4), and a post-move verification mismatch (b). Any one marks the task BLOCKED: preserve its worktree and branch (Step 4g must skip it), record it for the Completion Report, and continue with the remaining wave tasks. A merge conflict reported by gh pr merge (a step 5) is likewise BLOCKED — never force-merge; surface the conflict to the user.
Checkpoint (after each successful task delivery in the inner loop): use the STATE_SCRIPT resolver from State Init, then run python3 "$STATE_SCRIPT" task-merged <N> <task-name> — moves the task from pending to merged in wave <N> and stamps last_checkpoint.
Wave-end push for --draft and --aggregate-pr modes: After every task in the wave has been processed, the --defer-push completion-marker commits remain local. They are pushed once at the end of all waves (not per-wave), as part of the Completion Report transition. For --local-merge and --per-task-pr, no wave-end push is needed — every task already pushed individually (local-merge via finish-branch, per-task-pr via the (b) inline push after gh pr merge).
Failed (BLOCKED) tasks remain in <tasks-dir>/<task-name>; finish-branch never moves them. Record those tasks for the Completion Report.
Checkpoint (after the entire wave loop finishes): use the STATE_SCRIPT resolver from State Init, then run python3 "$STATE_SCRIPT" wave-complete <N> — flips wave <N> to completed (it refuses if any task is still pending, a built-in guard against marking an incomplete wave done) and stamps last_checkpoint.
4g. Clean Up Worktrees — Delete worktrees and branches for merged-and-marked tasks. This step is mandatory and verified, not best-effort. A leaked worktree pollutes Pre-flight on the next run, blocks reuse of the task name, and leaves the feature branch alive long after the work is on the base branch.
For each task whose Step 4e delivery completed (DONE):
$ywc-docker-isolate --mode teardown --task-name <task-name> --worktree-path <resolved-worktree-path>
# exit 0 = Docker stack removed or no stack to remove
# exit 1 = report LEAKED_DOCKER_STACK; do not roll back delivered code
$ywc-worktrees --mode prune --task-name <task-name> --branch feature/<task-name>
# exit 0 = PASS (worktree removed, branch deleted, prune done, verified)
# exit 1 = FAIL — fix hints on stdout; read them before taking any action
Run Docker teardown immediately before the delegated worktree prune and only inside this Step 4e-DONE loop. Tasks marked BLOCKED or intentionally preserved skip teardown together with prune, so their worktree-local Docker state remains available for recovery. A teardown failure after delivery is reported as LEAKED_DOCKER_STACK in the Completion Report; it does not undo the merge, completion-marker commit, or worktree prune attempt.
The delegated prune operation encodes all four failure paths (modified files, locked worktree, not-fully-merged branch, directory persists) and runs post-cleanup verification automatically. If exit 1, read the stdout output — it names the step that failed and includes the exact commands to investigate or resolve.
Preserve worktrees and branches of failed tasks for recovery. Do not run this step for any task whose Step 4e delivery did not complete (DONE). Record every task whose cleanup succeeded, was force-cleaned, or was deliberately preserved — Step 5 reports each category.
4h. Wave Cleanup Audit — Before transitioning to the next wave, audit the worktree state for the wave as a whole. Per-task verification in 4g catches single-task drift; this step catches wave-level drift (e.g. an agent created a sibling worktree the executor did not track).
# No preserved failures:
$ywc-worktrees --mode audit
# With preserved failures:
$ywc-worktrees --mode audit --expect <comma-separated preserved-failure task names>
# exit 0 = clean (or only expected preserved failures); exit 1 = DRIFT — investigate before next wave
If exit 1 (DRIFT), investigate before starting the next wave. Do not auto-delete unknown worktrees — they may belong to a concurrent operator or to a task whose state was not tracked.
4i. Transition to Next Wave — Per the Non-Stop Execution Principle, proceed to the next wave immediately and silently. Do not ask the user for confirmation. Do not emit any text between waves — no "Wave N complete", no status summary, no acknowledgment. The next output after Step 4h is the first tool call (creating worktrees) of Step 4a for the next wave. Before transitioning, run the executable terminal-state audit below — every task in the current wave must be in exactly one of two terminal states.
# Every task whose Step 4e delivery completed (DONE) must have its directory under completed/.
for task in <wave-success-tasks>; do
test -d <tasks-dir>/completed/$task || { echo "LEAKED: $task missing from completed/"; exit 1; }
done
# Every task whose Step 4e returned BLOCKED (preserved failure) must still be in <tasks-dir>/<task-name>.
for task in <wave-blocked-tasks>; do
test -d <tasks-dir>/$task || { echo "DRIFT: $task missing from <tasks-dir>/ (and not in completed/)"; exit 1; }
done
- Success → directory in
<tasks-dir>/completed/, worktree removed, branch deleted - Preserved failure → directory still in
<tasks-dir>/<task-name>, worktree and branch retained for recovery
No task should be in an in-between state (e.g. moved to completed/ but branch still alive, or worktree removed but directory not moved). If the audit reports LEAKED or DRIFT, do not transition — surface the offending task name to the user; transitioning forward with a missing Mark-Complete silently corrupts dependency resolution for every downstream wave.
Output Format (Step 5 Completion Report)
--draft and --aggregate-pr modes: Aggregate PR (execute before the report below)
Both modes accumulate all task changes locally on base-branch via wave merges with
--defer-push, then lift that state onto a single branch and open one PR. The full
command sequences live in references/aggregate-pr.md:
--draft→ §A: createdraft/<base>-<timestamp>, push, reset local base, open a draft PR, poll bots, re-verify CI, and stop (left open for a human to merge). Capture the PR URL for the report.--aggregate-pr→ §B: createaggregate/<group-name>(or timestamped), push, reset local base, open the PR, mark it ready, CI-verify, bot-review, pass the merge-readiness gate, merge (gh pr merge --delete-branch), and sync local base. The completion-marker commits ride into base through this single merge — do not re-Mark Complete. Capture the merged PR URL for the report.
Action required: Read references/aggregate-pr.md before executing either path — it carries the exact branch names, CI/bot/merge-readiness gates, and §C multi-group concurrency rules.
--per-task-pr mode: no end-of-run push — each task's PR was already merged via gh pr merge --delete-branch and its completion-marker commit was already pushed during the task's slot in the wave (Step 4e (a) step 5 and (b)). There is nothing deferred to flush here, and the individual PRs are merged and closed on the remote — not left open. Proceed directly to the report below.
Display the following after all waves are complete:
Total tasks executed, total waves
Each task: name, status (success/failed/skipped), merge commit SHA in
--local-mergemode, the aggregate PR URL in--draft/--aggregate-prmode, or the merged PR URL in--per-task-prmodeFailed/skipped tasks with reasons
In
--local-mergemode: remind that no PR was created, no remote CI ran, only local verification was performedIn
--per-task-prmode: each task's PR was created, CI-verified, bot-reviewed, and merged (gh pr merge); list each PR URL with its merged statusIn
--aggregate-prmode: the single aggregate PR was created, marked ready, CI-verified, bot-reviewed, merge-readiness checked, and merged; list the merged PR URLContract evidence: Changed Public Contracts, tests authored/executed, Critical Internals, and TDD Exceptions per task (or
N/A)Worktree cleanup status — one line per task, in one of these categories (omitted when
--terseis set):clean— worktree removed and branch deleted via Step 4g (the expected outcome for every successful task)force-cleaned—git worktree remove --forcewas required; note the reason (lock, disposable artifacts, etc.)preserved (failure)— intentionally retained for recovery; include the worktree path and branch name so the user can resume workLEAKED— Step 4g did not produce a clean result and the cause is not a recorded failure; this is a bug, surface it explicitly with the path and branch so the user can clean up manually
Run a final audit before printing the report:
# No preserved failures: $ywc-worktrees --mode audit # With preserved failures: $ywc-worktrees --mode audit --expect <comma-separated preserved-failure task names> git branch --list 'feature/*' | sed 's/^[* ] //' || trueAny worktree under the resolved worktree root or
feature/*branch that does not map to apreserved (failure)task in the report is a leak and must be reported asLEAKED, not silently omitted.
When --terse is set, omit all prose reminders, worktree audit lines, and mode-specific explanations. Emit only:
- The task table (name | status | merge SHA or PR URL)
- The
Completion Statusline
This is the preferred format for CI scripts or automation that parse the report output.
Reporting Symbols: Use the shared vocabulary in symbols.md for the per-task status column, the worktree cleanup status column, and the wave-level summary line. Parallel-specific addition: 🚨 for LEAKED worktree or branch detected by the final audit (Step 5) — surface explicitly, never reduce to ❌. Leaks are a distinct severity category the user must act on regardless of overall run status. For multi-step worktree lifecycle traces, use the flow operator » (e.g. worktree ✅ » impl ✅ » verify ✅ » merge ✅ » cleanup ✅).
Example wave summary row:
Wave 2 (3 tasks) | ✅ 2 ❌ 1 | force-cleaned: 0 preserved: 1 LEAKED: 0
Completion Status: End every report with one of these four declarations on its own line. This is the final line of the report — nothing follows it.
| Status | When to use |
|---|---|
DONE |
All tasks completed, all worktrees clean, no open issues |
DONE_WITH_CONCERNS |
Run completed but with caveats — failed tasks, LEAKED worktrees, advisor budget exceeded, or non-critical warnings |
BLOCKED |
Run stopped and cannot continue — merge conflict, circular dependency, push rejected, or a decision requiring human input |
NEEDS_CONTEXT |
Task list or arguments are ambiguous; the run cannot start without clarification |
Operational Self-Improvement: Before deleting the checkpoint file, append any genuinely new project-specific operational findings to .ywc-learnings.jsonl (one JSON object per line, format {"ts":"<ISO-8601>","skill":"ywc-parallel-executor","project":"<basename>","learning":"<one sentence>"}). Add the file to .gitignore if absent. Record only project-specific facts (package manager quirks, worktree isolation issues, build-step ordering, wave sizing observations); skip generic programming facts and anything already in CLAUDE.md. If nothing new in this run, skip the write entirely; empty entries are noise.
State cleanup: After displaying the report, delete the checkpoint file — the run is complete:
rm -f .ywc-run-state.json
Validation and Wave Failure Handling
- 1 task fails: Preserve its branch and worktree (do not run Step 4g for it), merge remaining successful tasks normally. Skip subsequent wave tasks that depend on the failed task. Record the preserved worktree path and branch name for the Completion Report.
- Merge conflict: Report conflicting files and related tasks, ask user to resolve manually. Do not auto-abort or force. The conflicting task's worktree and branch are preserved as in the single-task failure case.
- Cleanup failure (Step 4g): A failure in 4g does not roll back the merge — the code is already on the base branch. It does, however, mean the next run's Pre-flight will detect a stale worktree. Report the affected task as
LEAKEDin Step 5 with the exactgit worktree remove/git branch -dcommands the user should run, so cleanup is one paste away. - Full stop conditions: Pre-flight failure, circular dependency detected,
git pushrejected
Integration
- upstream: ywc-task-generator
- downstream: ywc-impl-review (with --review), ywc-handle-pr-reviews (bot review handling in --per-task-pr), PR creation + merge