---
name: run-plan
description: >
  Use when the user runs "/run-plan <PLAN_SLUG>" or when any skill needs to execute or
  resume a plan — unified plan executor. Single engine: released v2 bundle (`runplan` on PATH → `~/.harness/engine/versions/<CURRENT>`),
  fail-closed, resumable via its own journal. Single entry point; replaces /resume-plan.
---

# run-plan

audience: AI coding agents first. Imperative, BLUF.

MUST follow `/home/user/Projects/0 DOCS/GIT_FATIGUE.md` §12. Delivery controller owns workspace, validation, publication, and cleanup. Valid current-tree receipt/log = proof; NEVER rerun broad checks. User surface MUST be only `Preview ready` or `Needs product decision`.

NOT the factory: `factory <adw> "request"` self-plans from one sentence and never consumes plan docs — see `factory` skill. This skill executes AUTHORED plan JSONLs only.

Thin launcher. `runplan` (PATH shim → released v2 engine bundle) IS the engine — a plain CLI process that drives its own wave/gate0/journal/commit cycle and blocks until it halts or completes. Do NOT re-implement the loop here. v1 (`mega-plan-harness` `src/runner.js`, `runplan-legacy`) is DEAD — hard cutover 2026-07-29; NEVER launch it.

## Inline fallback (no engine) — trivial single-wave plans only

User asks to execute a plan inline / plan is single-wave sequential with no parallel tasks → skip the engine, execute in-session (absorbs the former `executing-plans` skill):

1. Read plan, review critically — concerns → raise with user BEFORE starting; none → TodoWrite, proceed.
2. Set up worktree via `using-git-worktrees`. NEVER start on main/master without explicit user consent.
3. Per task: mark in_progress → follow steps exactly → run the task's verifications → mark completed. Blocker / failing verification / unclear instruction → STOP and ask, NEVER guess or force through.
4. All tasks verified → land via `finishing-a-development-branch`.

Parallel tasks in any wave → `ship` skill. Multi-wave, unattended, or resumable → the engine below.

## On `/run-plan status`

First token of `$ARGUMENTS` is `status` → this is the on-demand global pull, NOT a plan launch. Run it and show the user the raw output verbatim; do nothing else:

```bash
node ~/.claude/hooks/lib/run-plan-scan.mjs --mode=report
```

It prints every stalled run-plan run anywhere under `~/Projects`, with progress **reconciled against git** (the accurate count — a raw journal count under-counts because the commit seam can land code before the journal entry is written). `state` column: `this session` (you own it), `owner DEAD` (orphaned, resumable), `legacy (verify)` (pre-beacon, verify before acting). Healthy runs another live session owns are excluded. Add `--repo=<absPath>` to scope to one repo.

## On `/run-plan <PLAN_SLUG> [flags]`

1. **Slug = `$ARGUMENTS`** first token. Empty → ask which plan; list `docs/plans/*.jsonl` stems across `~/Projects/*`.
2. **Flags** (optional, parse from the rest of `$ARGUMENTS`):
   - `--isolate` → run this plan in a SEPARATE repo CLONE, land via `pr` — safe to run in PARALLEL with another live run on the same repo. Triggers **§ Isolated run** below BEFORE step 3. Omit for the normal single-run-per-repo case (per-task worktrees already isolate one run fully). Never `--isolate` expecting `merge-to-main` — isolation forces `pr`.
   - `--preset <name>` → passed straight through to `runplan` (overrides the plan's `meta.preset` for this run; plan file untouched; unknown preset fails loudly before run state exists).
   - `--account <slug>` → passed straight through to `runplan` (every wrapper invocation gets `--profile <slug>`; picks the codex account).
   - `--concurrency N` → passed straight through to `runplan`.
3. **Pre-flight (cheap, MUST pass):** confirm a session file exists — glob `~/Projects/*/docs/plans/*-<PLAN_SLUG>.jsonl`. None → STOP, list available slugs, do not launch. Native Read the JSONL (`meta` line has no `schema` key — harness-native, `spec/runconfig.schema.json` in the target repo).

## Decision gate (MUST, before any launch)

Full `gated` record schema → `brainstorm/SKILL.md` "Session File".

1. Native Read the plan JSONL. Collect all `gated` records with `status:"OPEN"`.
2. Any OPEN → present ONE `AskUserQuestion` sheet. Each entry shows `needs` / `why` / `blast_radius` / `options`, recommended option first. Every go/no-go entry MUST offer exactly `proceed` / `abort` — `abort` is the SOLE stop value the engine honors (any other answer PROCEEDS). A fork entry offers its branch values instead (all proceed-semantics, injected as the task's input).
3. Write each answer back to its `gated` line via a small `jq`/`python3` in-place edit (single-line rewrite): `status:"RESOLVED"`, `answer:<choice>`, `resolved_by:"user"`. For a `binds_meta` decision, ALSO write the answer into the named `meta` field.

**Scope, stated plainly:** this covers plan-authored gated records only — no LLM scan backstop over task descriptions yet (a real gap, tracked as a follow-up, not silently dropped).

## Ship-method freeze (MUST, before launch)

`runplan` (v2 engine) has no ship/land phase of its own — the project's frozen wrapper `<repoRoot>/.claude/scripts/ship.sh` is the ONLY thing that lands the work, and it is the SAME brain the `ship` skill uses. Method (`pr` | `merge-to-main` | `deploy-verify`) is per-project and is normally DECIDED + stamped at PLAN-AUTHOR time (`meta.land_mode`, see [[brainstorm]]); the run itself never decides it. This step just MATERIALIZES the machine-local wrapper from that record — a near-noop when `land_mode` is already stamped and the wrapper already exists. The wrapper is machine-local (gitignored, absolute brain path baked in) — `meta.land_mode` in the shared jsonl is the RECORDED answer, NOT proof the wrapper exists on THIS machine. Ladder — stop at the first rung that holds:

1. Wrapper `<repoRoot>/.claude/scripts/ship.sh` exists AND `bash <repoRoot>/.claude/scripts/ship.sh drift` exits 0 → proceed. (Existence alone is NOT validity — a wrapper from a stale machine / restructured repo / moved deploy anchor exists but has DRIFTED. Drift non-zero ⇒ do NOT proceed; fall to rung 2 to regenerate.)
2. Wrapper absent OR drifted, AND `meta.land_mode` is a RECOGNIZED value (`pr` | `merge-to-main` | `deploy-verify` — a literal placeholder like `<pr|merge-to-main…>` copied from a schema line counts as ABSENT → rung 3) → REGENERATE non-interactively from the STAMPED facts (do NOT re-ask, do NOT re-sniff): `bash ~/.claude/workflows/lib/ship-init.sh <repoRoot> --mode <meta.land_mode>`; for `deploy-verify` ALSO append `--deploycmd <meta.land_deploycmd> --e2ecmd <meta.land_e2ecmd> --promote <meta.land_promote>` VERBATIM. Re-sniffing here would drift (`promote` is a user choice sniff only biases; cmds may have been overridden) and `drift` would not catch it → fail-late at deploy. Re-run rung 1's `drift` to confirm the regenerated wrapper validates, then proceed.
3. Wrapper absent/drifted and no recognized `meta.land_mode` → delivery controller resolves internal publication mechanism from project policy. NEVER surface PR/merge/push choice to owner. Product-impact fork only → `Needs product decision`.

**Run this ladder EVERY launch (incl. resume routes, which all re-enter `/run-plan`).** Cheap, no agent dispatch. The engine's final ship step is the fail-closed safety net: a missing/invalid wrapper makes `ship.sh land` exit non-zero → HALT (never a silent or wrong land). NEVER launch without a frozen method.

## Launch

1. **Preset:** `meta.preset` on the plan's meta line, overridable per launch with `--preset <name>`. Neither present → stamp `codex` into the meta line before launch.
2. **Run (foreground), from inside `<repoRoot>`:**
   ```bash
   cd <repoRoot> && runplan <PLAN_SLUG> [--preset <name>] [--account <slug>] [--concurrency N]
   ```
   `runplan` is PATH-installed (no repoRoot prefix) — it resolves the target repo from cwd and the plan via `docs/plans/<PLAN_SLUG>.jsonl` or the unique `docs/plans/*-<PLAN_SLUG>.jsonl` match. Engine code resolves from the released bundle (`~/.harness/engine/CURRENT`), independent of cwd. v2 flags: `[--preset <name>] [--account <slug>] [--concurrency N] [--retry-blocked] [--budget-ms N] [--run-id <id>]`; `--daemonize` and `--supervised` are REJECTED by the CLI (no daemon/supervised launch exists); dead v1 flags (`--profile --runconfig`) DO NOT EXIST — the CLI fails loudly on them (`--profile` → use `--account`). The run arms the v2 control-api so overdeck /plans watches it. Exit 0 → step 3. Non-zero → HALT, surface the engine's own stderr verbatim — do not retry blindly or reinterpret it.
3. **Publish:** call project delivery controller; agents MUST NOT run raw publication commands.
4. **Tell user:** only `Preview ready` with inspection target + expected result, or `Needs product decision` with one concrete product decision + impact.

## Resume / crash

Same command, from inside `<repoRoot>`: `runplan <PLAN_SLUG>`. A task left `blocked` by a prior run is skipped on plain relaunch — add `--retry-blocked` to re-attempt it. The engine journal under `runstate/v2/` (registry `~/.harness/v2/runs/`) is the sole resume ground truth — the runner reconciles it against git reachability on every invocation (a `committed` entry that isn't actually reachable gets downgraded automatically). No separate cold/warm resume procedure to choose between.

**Resume preserves committed task work.** A task that HALTed *after* committing fixes (e.g. review hit its retry cap) leaves them on its task branch `plan/<slug>--<id>` (FLAT double-dash, collision-free vs integration `plan/<slug>`). The lease detects a task branch ahead of integration and RESUMES it — skips re-implement, re-enters the gate/review ladder on the existing diff. **Escape hatch — force a clean redo:** to discard a garbage committed draft and re-implement from scratch, delete the task branch first: `git -C <repoRoot> branch -D plan/<slug>--<id>` (and `git -C <repoRoot> worktree remove /tmp/wt-<slug>-<id> --force` if it lingers). Next lease sees the branch ABSENT → fresh implement off integration.

**An isolated run MUST be resumed WITH `--isolate`** — re-run **§ Isolated run** first (setup REUSES the existing clone) before the launch above.

## Isolated run (`--isolate`)

Triggered by the `--isolate` flag (step 2). Runs the plan against a CLONE with its own `.git`, landing via `pr` so it NEVER touches the primary's base — the only safe way to run two plans against one repo at once. All mechanics live in ONE tested fail-closed script; do NOT re-derive clone/seed/land steps as prose.

**Run this BEFORE the decision gate, then rebind every later step to the clone:**

1. Materialize the clone (idempotent — a resume REUSES the existing clone):
   ```bash
   bash ~/.claude/workflows/lib/rp-isolate.sh setup <PLAN_SLUG>
   ```
   It clones origin, seeds the uncommitted plan jsonl/md + spec anchors, flips the clone jsonl's `land_mode` to `pr`, and freezes a `pr` ship wrapper in the clone. Emits ONE JSON line: `{"clone","projects","jsonl","base","reused"}`. `ok=false`/non-zero exit → STOP (it fail-closes on: no origin, local base ahead of origin, no land testcmd). Do NOT improvise a clone by hand.
2. **Rebind — operate on the CLONE, NEVER the primary repo.**
   - Plan JSONL to Native-Read + write gate answers to = the `jsonl` path from setup (the clone copy).
   - Ship-method freeze targets the clone's `.claude/scripts/ship.sh`, already frozen `pr` by setup (rung 1's `drift` passes — a near-noop confirmation). NEVER let it run against the primary.
   - Invoke from INSIDE the clone: `cd <clone> && runplan <PLAN_SLUG>` — `runplan` resolves target repo from `git rev-parse --show-toplevel` of cwd, so running from inside the clone naturally binds to it.

**Teardown is manual + guarded — NEVER auto-removed.** After the PR merges:
```bash
bash ~/.claude/workflows/lib/rp-isolate.sh teardown <PLAN_SLUG>   # refuses any path without the rp-isolate sentinel
bash ~/.claude/workflows/lib/rp-isolate.sh gc --days 7            # sweep stale clones
```

**Tell the user:** running isolated in a clone at `<clone>`; lands via PR against `<base>` (primary repo + its live run untouched); clone persists for inspection — `rp-isolate.sh teardown <slug>` after the PR merges.

## Safety floor — state plainly, every launch

- Fully unattended: it auto-commits per task, integrates on `plan/<slug>`, then ships via the project's frozen method (chosen + confirmed pre-launch). `pr` → opens a PR, `main`/`master` untouched (you merge). `merge-to-main` → gated fast-forward + push base. `deploy-verify` → deploy preview → e2e gate → promote on green; NEVER promotes an unverified build.
- **Fail-closed:** any gate failure, unmet dep, or HALT condition → the runner stops and reports; it never auto-skips. Resolve the HALT, re-run the same command.
- First run on a new plan → recommend a small 2–3 wave plan to observe the loop before trusting a long one.

## Do NOT

- Re-author the reconcile/gate logic in the conversation — that defeats the engine's whole purpose (state stays in the v2 engine, off the main thread). Launch `runplan`; don't reimplement it.
- Drive the loop turn-by-turn yourself. The runner owns the loop; it blocks in the foreground until it halts or completes.
