You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,15 @@ All notable changes to **superpowers-v (Compound V)** are documented here.
4
4
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses semantic versioning.
6
6
7
+
## [2.6.1] — 2026-07-06
8
+
9
+
### Fixed — worktree git-base fixes are the caller's job, never the worker's
10
+
11
+
-**Closed a real incident from a downstream repo:** a parallel-dispatch batch assigned a job to Codex, but the job's worktree needed its git base fixed — Codex's sandbox is confined to `$WT`, while the worktree's actual git metadata lives *outside* it, in `<repo>/.git/worktrees/<job-id>/`, and `approval_policy: never` means it can't ask to escalate. A **sandbox limitation, not a code one**. The orchestrator worked around it by dropping worktree isolation for Codex — which is **not** a fix: it removes the only file-scope enforcement Codex has (`codex ⇒ worktree` is a hard invariant in `compound-v-validate-manifest.py` precisely because Codex can only be confined to a *directory*, never a file allow-list), and risks interleaved writes if other jobs are running concurrently in the same tree.
12
+
-**The correct fix, now explicit:** every dispatch or retry of an external worker (Codex/Antigravity/Cursor) goes through the **full worker-script lifecycle**, which already recreates the worktree fresh at current HEAD every time — never patch an existing worktree's git state, and never delegate that patch to the worker itself. A job that needs another job's already-landed output must model that as `depends_on` in the manifest, not discover it mid-run.
13
+
- New `SKILL.md` §**Worktree git-base fixes** (the shared, mechanism-level explanation), a cross-reference in `adapter-codex.md`, and explicit language in `parallel-dispatcher.md`'s isolation step and its retry line (which previously just said "re-dispatch the same backend" without specifying the worktree is recreated fresh — the exact ambiguity that let the workaround slip through). Docs-only; no code changed.
14
+
- **Codex cross-model verification, two rounds, caught real gaps in the fix itself.** Round 1 confirmed the core sandbox mechanism but found the `depends_on` guidance **overclaimed**: merge-back only *stages* a job's changes (`git apply --index`) — it never commits, so `HEAD` doesn't move, and a dependent job's "fresh worktree at `HEAD`" would **not** contain a prerequisite's merged-but-uncommitted work. Fixed: `parallel-dispatcher.md` Step 1 now requires the caller to **verify Task 0's result is actually committed** (for both `direct` and `worktree` isolation) before Step 2 begins — the missing link between `depends_on` and a correct fresh-worktree baseline. Round 1 also flagged an overclaim that Codex "cannot touch outside metadata even with `--dangerously`-style flags" — narrowed to the documented pinned invocation only. **Round 2 confirmed the round-1 fixes, then caught one more:** the wording assumed a `direct`-isolation implementer always commits its own work — `adapter-claude.md` establishes only that it writes against the main tree, not that it commits — fixed to an explicit caller-side verify-and-commit step for both isolation modes. Two full review rounds, three real corrections, all fixed.
Copy file name to clipboardExpand all lines: agents/parallel-dispatcher.md
+4-3Lines changed: 4 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -58,7 +58,8 @@ If the manifest has a `type: shared_foundation`, `run: serial` job:
58
58
- Dispatch ONE job by its manifest backend, resolving its model first via `compound-v-resolve-model.py` (Task 0 routes `claude · tier: deep · direct` ⇒ **opus** in every stance — cheap models miscall shared types/migrations).
59
59
- On return, run the **scope gate** (Step 2b) and write `state.json`.
60
60
- Wait for completion. Dispatch one spec-reviewer (`compound-v:spec-reviewer`) and one code-quality reviewer, both Opus. Address feedback; re-dispatch Task 0's implementer if reviewers found issues.
61
-
- Only proceed to Step 2 when Task 0 is fully approved (every parallel job `depends_on` it).
61
+
-**Verify Task 0's result is actually COMMITTED before proceeding — do not assume it.** For `worktree` isolation, merge-back only *stages* the change (`git apply --index` does not commit) — the caller must `git commit` it. For `direct` isolation, the subagent writes in place but is **not guaranteed** to commit its own work ([`adapter-claude.md`](../skills/backend-launcher/adapter-claude.md) establishes only that it writes against the main tree, gated by a baseline commit for the scope gate — not that it commits) — check `git status`/`git log` and commit it yourself if it didn't. This is not optional either way: every `run: parallel` job `depends_on` Task 0 and gets a **fresh worktree at current HEAD**, which only contains Task 0's work if that work is an actual commit, not merely staged or dirty in the working tree.
62
+
- Only proceed to Step 2 when Task 0 is fully approved **and committed** (every parallel job `depends_on` it).
62
63
63
64
### Step 2 — Parallel Implementer Batch(es)
64
65
@@ -103,7 +104,7 @@ Always show the **resolved** model (`backend · model (tier/effort)`), never the
103
104
-**Explicit manifest `model:` override skips resolution.** If a job entry carries an explicit `model`, do NOT run the resolver for it — that model wins (pass it straight through, or call the resolver with `--explicit-model <M>` which short-circuits to it). This preserves backward compatibility with existing explicit-model jobs.
104
105
105
106
A `claude` job resolves `deep`→opus, `standard`→opus (sonnet under `cost-aware`), `light`→sonnet — `"sonnet"` for a `standard`-tier job only under the `cost-aware` stance, and otherwise ONLY where the manifest routed the job `light` AND partition-reviewer's PASS confirmed it. Reviewer jobs always resolve to `tier: deep` (⇒ opus). The resolution above is **execution-layer** and unrelated to this agent's own `model: opus` frontmatter.
106
-
2.**Isolation from the manifest** — `direct` for clean in-harness Claude jobs (gated against a baseline commit), `worktree` for risky/broad-surface Claude jobs and **always** for Codex.
107
+
2. **Isolation from the manifest** — `direct` for clean in-harness Claude jobs (gated against a baseline commit), `worktree` for risky/broad-surface Claude jobs and **always** for Codex/Antigravity/Cursor. **Never patch an existing worktree's git state, and never ask the external worker to fix its own worktree's git base (rebase/reset/fetch) — that is a caller-side operation, not the worker's** (mechanism + rationale: [`backend-launcher/SKILL.md`](../skills/backend-launcher/SKILL.md) §Worktree git-base fixes). Every dispatch — first attempt **or retry** — MUST go through the backend's full worker-script lifecycle (create → run → observe → merge/remove), which recreates the worktree **fresh at current HEAD** every time; never shortcut by re-invoking the CLI directly against a worktree left over from a prior attempt. If a job's task genuinely depends on another job's *already-landed* output, model that as `depends_on` in the manifest — do not let a job discover the dependency mid-run and try to patch its own base. **`depends_on` only works if the prerequisite's merge-back was committed** — merge-back stages the change (`git apply --index`) but does not commit, so `HEAD` doesn't move; `git worktree add <WT> HEAD` checks out the last *commit*, not the caller's staged state. Commit a prerequisite's merged result before creating any worktree for a job that `depends_on` it (see Step 1 below).
107
108
3.**Turn/time bound** — `maxTurns: 15` on Claude Task calls; `timeout_sec` in the `job_spec` for Codex workers. A job that hasn't finished in 15 turns is usually stuck and needs re-dispatch with more *context*, not more turns.
108
109
4.**`job_spec`** — `{ backend, prompt, tier, effort?, model (resolved or explicit override), cwd (absolute), write_allowed, read_only, timeout_sec, network, output_schema? }`, exactly the [`backend-launcher`](../skills/backend-launcher/SKILL.md) input. The `model` is the value the resolver returned in step 1 (or the explicit manifest override); `tier`/`effort` carry the intent forward.
109
110
5.**Prompt content** (captured verbatim to `jobs/<id>.prompt.md` for resume) must include:
@@ -179,7 +180,7 @@ A `job_result.status` that is **not** `success` and **not** `blocked` is a backe
179
180
-`--current-tier <resolved tier>` — so a `context_length` failure escalates to a bigger tier **unless already at the deepest tier** (`deep`), where it returns `halt` (split the job) rather than escalating into a model that doesn't exist.
180
181
181
182
3.**Act** on `action`:
182
-
-**`retry`** → **first record the cooldown so resume/half-open is deterministic**: write `cooldowns[<backend>] = <now + backoff_seconds>` (epoch/ISO) in `state.json` — this is the timestamp the half-open/`/v:resume` logic reads, so the retry path MUST produce it. Bump `attempts[<job>][<failure_class>]` (the per-class counter) and `total_retries`. Then **sleep `backoff_seconds`** (the policy's value — already the provider's `retry-after` when one was passed) and re-dispatch the **same** backend (replay `jobs/<id>.prompt.md`). Re-run the scope gate on return.
183
+
-**`retry`** → **first record the cooldown so resume/half-open is deterministic**: write `cooldowns[<backend>] = <now + backoff_seconds>` (epoch/ISO) in `state.json` — this is the timestamp the half-open/`/v:resume` logic reads, so the retry path MUST produce it. Bump `attempts[<job>][<failure_class>]` (the per-class counter) and `total_retries`. Then **sleep `backoff_seconds`** (the policy's value — already the provider's `retry-after` when one was passed) and re-dispatch the **same** backend (replay `jobs/<id>.prompt.md`)**through the full worker-script lifecycle** — for an external worker (Codex/Antigravity/Cursor) this means the worktree is removed and recreated fresh at current HEAD before the retry runs, exactly as the adapter's create step already does; never resume by poking the CLI at the job's old worktree directly. Re-run the scope gate on return.
183
184
-**`reroute`** with `circuit_break: true` (out_of_credits) → open the breaker **object**`circuit_open[<backend>] = {"open": true, "reason": "out_of_credits", "opened_at": "<iso-ts>", "cleared_by": null}` and re-route **this job AND every remaining same-backend job** in the run via the env-aware **codex→claude** rewrite ([`routing-policy.md`](../skills/compound-v/routing-policy.md) §Env-aware Claude-only fallback) — the SAME rewrite `/v:init` uses when Codex is absent, here at runtime. **Announce it loudly** (see Output): never silently swap a cheap backend for an expensive one.
184
185
-**`reroute`** with `escalate_tier: true` (context_length, not yet at the deepest tier) → re-resolve the job at a **bigger tier** via `compound-v-resolve-model.py` and re-dispatch. When the job is re-routed to a different backend or its class changes, **reset/fork** its per-class attempt counter.
185
186
-**`halt`** → mark the job `failed` in `state.json`, keep the run **`/v:resume`-able**, and **continue other independent jobs** (ralph-tui-style: a sibling's 429 must not kill unrelated jobs). Two round-2 cases also return `halt` and must be honored, not retried:
**A bare `codex`/`cursor`/`agy` call — no supervisor, or no `</dev/null` — is a bug.** The dispatcher's [liveness sweep](../compound-v/state-machine.md)*detects* a hang after the fact; this launch rule *prevents* it. (All three worker scripts already comply; `compound-v-codex-review.sh` was brought under the supervisor in v2.5.0.)
89
89
90
+
## Worktree git-base fixes — the CALLER's job, never the worker's (non-negotiable)
91
+
92
+
**Never ask an external worker (Codex/Antigravity/Cursor) to fix its own worktree's git base** (rebase, reset, fetch, or any other repair of the worktree's git plumbing). If a worktree's base is wrong — stale relative to a merged prerequisite, or otherwise needs correcting — that is resolved by the **caller** recreating the worktree, never by instructing the worker to patch it mid-run.
93
+
94
+
Two independent reasons this must stay caller-side, not worker-side:
95
+
- **Every external worker already recreates its worktree fresh at current HEAD on every invocation** (each adapter's create step: remove any stale worktree at that path, then `git worktree add <WT> HEAD` — "idempotent on resume", documented per-backend in `adapter-codex.md` / `adapter-cursor.md` / `adapter-antigravity.md`). A job that needs a different base — e.g. it depends on another job's *already-merged* output — needs that modeled as `depends_on` in the manifest so the caller dispatches it in the right order, not patched after the fact. **But `depends_on` alone is not enough — the caller MUST commit the prerequisite's merged output before creating the dependent job's worktree.** Merge-back (`git apply --index`) only *stages* a job's changes into the caller's tree; it does **not** commit, so `HEAD` does not move. `git worktree add <WT> HEAD` checks out the last **commit**, not the caller's currently-staged/uncommitted state — so if the prerequisite's work is only staged, not committed, the dependent job's "fresh worktree at HEAD" will **not** contain it. Always commit a prerequisite's merge-back result before dispatching anything that `depends_on` it (see `parallel-dispatcher.md` Step 1→2).
96
+
-**Codex specifically cannot do it even if asked**, under the documented pinned invocation (`--sandbox workspace-write --cd "$WT"`, no sandbox-bypass flag). A git worktree's `.git` is a *file* pointing at `<main-repo>/.git/worktrees/<name>/`, where the actual per-worktree git metadata (`HEAD`, index, etc.) physically lives — **outside** the worktree directory itself. Codex's sandbox confines writes to `$WT` only, so any git operation touching that metadata falls outside the sandbox root; combined with `approval_policy: never` (no one to ask for escalation — see the launch rule above), the operation is simply not permitted under that invocation. This is a **sandbox limitation, not a code one** — dropping worktree isolation to work around it is not a fix, it removes the only file-scope enforcement Codex has (`codex ⇒ worktree` is a hard invariant in `compound-v-validate-manifest.py`, precisely because Codex can only be confined to a *directory*, never to a file allow-list).
97
+
98
+
If a job ever appears to need a git-base fix mid-run, that is a signal the run's dependency ordering is wrong (missing `depends_on`, or a prerequisite's merge-back was never committed) or a retry skipped the worker's normal create step — fix the manifest/commit the prerequisite or re-dispatch through the full lifecycle; never patch the worker's worktree by hand or delegate the patch to the worker itself.
Worktrees live **outside the repo**, under `"${TMPDIR:-/tmp}"/compound-v/<run-id>/<job-id>` — so no `.gitignore` change is needed. The script is **idempotent on resume**: if a worktree already exists at that path (e.g. a re-dispatched job), it is `worktree remove -f`'d (falling back to `rm -rf`) before a fresh `add HEAD`. The script itself never calls `worktree remove` on success — it leaves the worktree in place so the caller can merge from it; removal is the caller's responsibility after a successful `git apply`.
109
109
110
+
**This create step is the ONLY correct way to fix a wrong worktree base — never ask Codex to rebase/reset it itself.** Under this adapter's documented pinned invocation (`--sandbox workspace-write --cd "$WT"`, no sandbox-bypass flag), Codex's sandbox is confined to `$WT`, but a worktree's actual git metadata lives in `<REPO>/.git/worktrees/<job-id>/` — outside `$WT` — so that operation is outside the sandbox root, and `approval_policy: never` means it can't ask to escalate either. See [`SKILL.md`](SKILL.md) §Worktree git-base fixes for the full rationale and why dropping worktree isolation is not the fix.
0 commit comments