Skip to content

parallel-planner-with-review: a branch ahead of HEAD from an earlier cycle reaches the merger without review #1628

Closed

Description

@mattpocock

What happens

In the parallel-planner-with-review template, the reviewer only runs when this cycle's implementer made commits:

// src/templates/parallel-planner-with-review/main.mts, Phase 2
// Only review if the implementer produced commits
if (implement.commits.length > 0) {
  const review = await sandbox.run({ name: "reviewer", ... });

Since #1531, the merge phase no longer looks at this cycle's commits. It passes the merger every fulfilled branch that is ahead of HEAD (isAheadOfHead, i.e. git rev-list --count HEAD..<branch> > 0), whichever cycle made the commits.

Those two gates now disagree, and a branch can reach the merger without ever being reviewed:

  1. Cycle N: the implementer commits on issue-42. The reviewer then fails (network error, sandbox crash, the agent erroring out), so the pipeline promise rejects and the branch is left out of the merge.
  2. Cycle N+1: the planner picks issue 42 again, since it's still open. The implementer finds the work already on the branch and makes no new commits, so implement.commits.length === 0 and the reviewer is skipped. The pipeline fulfils.
  3. The merge phase sees that issue-42 is ahead of HEAD and hands it to the merger, with no review.

The same happens when the implementer itself commits and then throws in cycle N: in that case its commits are never reviewed at all.

Where

  • src/templates/parallel-planner-with-review/main.mts: the implement.commits.length > 0 gate in Phase 2, compared with the isAheadOfHead filter before Phase 3.
  • src/templates/parallel-planner/main.mts has the same isAheadOfHead merge gate but no reviewer, so it isn't affected. sequential-reviewer creates a fresh branch every iteration and has no merge phase, so it isn't affected either.

Expected

A branch that is ahead of HEAD and hasn't passed review gets reviewed before it can merge. In practice, every branch the merger receives in a cycle has had a reviewer run complete on it in that same cycle.

Prior work


This issue was written by an AI agent (Claude Code).

Activity

  1. xg-gh-25 commented on Nov 11, 2026

    @xg-gh-25

    The two gates are asking different questions: the reviewer asks "did this cycle produce commits?", the merger asks "is the branch ahead of HEAD?". A gate keyed on this-run activity will always be bypassed by retries, because a retry is exactly the case where the work already exists. Both gates need to key on the same object: the branch tip being merged.

    Something like:

    // Phase 2: review whenever the branch has unmerged work, not when this cycle committed
    if (await isAheadOfHead(branch)) {
      const review = await sandbox.run({ name: "reviewer", ... });
      // record only after the reviewer run completed
      reviewedTip.set(branch, await revParse(branch));
    }
    
    // Phase 3: merge only what was reviewed, at the exact tip being merged
    const mergeable = [];
    for (const b of fulfilled) {
      if ((await isAheadOfHead(b)) && reviewedTip.get(b) === (await revParse(b))) mergeable.push(b);
    }

    Recording the reviewed SHA instead of a boolean also covers the cases in the issue where commits land after the last review (implementer commits then throws, or anything commits after the reviewer ran): the tip moves, the marker no longer matches, and the branch can't reach the merger. Since reviewedTip is per-cycle, it also matches the stated expectation that every branch the merger gets was reviewed in that same cycle. If you later want to skip re-reviewing an unchanged branch across cycles, persist the marker as a ref (refs/reviewed/issue-42 -> sha) rather than in-memory state.

    The general rule we've landed on: a gate is only mechanical if its predicate is computed from the artifact being let through, not from what happened during the run.


    From running a review-before-merge gate in SwarmAI's autonomous pipeline. Discussion: Mechanical Gate vs Ceremonial Gate

  2. added a commit that references this issue on Nov 12, 2026
    0612e9a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingready-for-agentFully specified, ready for AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions