Skip to content

Commit 597594f

Browse files
committed
merge: v2.6.1 — worktree git-base fixes belong to the caller, never the worker
2 parents 7812f5f + 82e2049 commit 597594f

6 files changed

Lines changed: 27 additions & 5 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
{
1010
"name": "superpowers-v",
1111
"description": "Compound V for Superpowers: triple parallel pre-flight (code archaeology + domain-expert + Context7 library validator), disjoint partitioning, manifest-driven multi-backend dispatch (Claude + Codex + Antigravity + Cursor), git-diff scope enforcement, crash-resumable runs, adaptive tier-based routing, epic mode, V-memory local-first semantic+lexical recall over docs/superpowers (opt-in pure-python embeddings + a deterministic recall→action bridge), and batched parallel dispatch (Opus default, narrow Sonnet exception)",
12-
"version": "2.6.0",
12+
"version": "2.6.1",
1313
"source": "./",
1414
"author": {
1515
"name": "Oleg",

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "superpowers-v",
33
"description": "Compound V for Superpowers: triple parallel pre-flight (code archaeology + domain-expert advisor + library/doc validator via Context7), disjoint file partitioning, manifest-driven multi-backend dispatch (Claude + headless Codex + Antigravity + Cursor workers), git-diff scope enforcement, crash-resumable runs, adaptive tier-based routing, epic mode for multi-feature builds, V-memory local-first semantic+lexical recall over docs/superpowers (opt-in pure-python embeddings + a deterministic recall\u2192action bridge), and batched parallel dispatch (Opus default, Sonnet for narrow junior-task carve-out), plus /v:onboard — a project-onboarding command that builds a citation-verified knowledge base + AGENTS.md/CLAUDE.md bridge behind a human gate. Auto-intercepts brainstorming \u2192 writing-plans \u2192 execution transitions.",
4-
"version": "2.6.0",
4+
"version": "2.6.1",
55
"author": {
66
"name": "Oleg",
77
"email": "copeus@gmail.com"

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ All notable changes to **superpowers-v (Compound V)** are documented here.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses semantic versioning.
66

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.
15+
716
## [2.6.0] — 2026-07-06
817

918
### Added — `pr-review` skill + `/v:pr-review` command

agents/parallel-dispatcher.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ If the manifest has a `type: shared_foundation`, `run: serial` job:
5858
- 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).
5959
- On return, run the **scope gate** (Step 2b) and write `state.json`.
6060
- 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).
6263

6364
### Step 2 — Parallel Implementer Batch(es)
6465

@@ -103,7 +104,7 @@ Always show the **resolved** model (`backend · model (tier/effort)`), never the
103104
- **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.
104105

105106
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).
107108
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.
108109
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.
109110
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
179180
- `--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.
180181

181182
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.
183184
- **`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.
184185
- **`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.
185186
- **`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:

skills/backend-launcher/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ python3 scripts/compound-v-run-with-timeout.py --timeout <sec> --grace 3 -- <cli
8787

8888
**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.)
8989

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.
99+
90100
---
91101

92102
## Worker prompt lock (planner/executor separation)

skills/backend-launcher/adapter-codex.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ git -C "$REPO" worktree remove -f "$WT"
107107

108108
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`.
109109

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.
111+
110112
---
111113

112114
## Merge-back (caller, step 6)

0 commit comments

Comments
 (0)