Skip to content

fix(agent-actions): approval-queue accept replays a staged merge with no live CI/mergeable/review re-check #2126

Description

@JSONbored

Parent: #1936

Problem

All CI-green / mergeable-clean / gate-passed / approvals-satisfied preconditions for a merge are computed once, at staging time, inside planAgentMaintenanceActions (src/settings/agent-actions.ts:331: canMerge = reviewGood && !heldForManualReview && acting("merge") && mergeableClean && approvalsSatisfied && !mergeTerminallyBlocked). For autonomy: auto_with_approval, this evaluation happens exactly once at staging time; the persisted pending row (AgentPendingActionParams, src/types.ts:676-686) carries only { mergeMethod, expectedHeadSha } — no CI state, mergeable state, or reviewDecision.

On accept, decidePendingAgentAction (src/services/agent-approval-queue.ts:20-84) performs exactly one freshness check — head SHA equality (lines 44-56) — then replays the action verbatim through executeAgentMaintenanceActions. That executor's only further guard, fetchPullRequestFreshnessclassifyPullRequestFreshness (src/github/pr-freshness.ts:32-55), checks only state === "open" and head-SHA equality — never mergeable_state, CI status, or reviewDecision. The live-fetch helpers that could supply this (fetchLiveMergeableState, fetchLiveReviewDecision in src/github/backfill.ts) exist but have zero callers anywhere in src/.

Failure scenario: a PR is staged for auto_with_approval merge while CI is green and mergeableState is clean. auto_with_approval staged rows have no expiry — before a maintainer clicks accept (hours or days later), the base branch advances and mergeable_state recomputes to dirty, or a non-required check flakes to failing on a re-run, or a reviewer submits CHANGES_REQUESTED. None of these move the head SHA. The maintainer accepts; both freshness checks pass (same head, still open); mergePullRequest fires with the stale justification. GitHub's own branch-protection can catch some of this (a real conflict, or a required check regression) — but only for repos that configure it, and never for a non-required check or a review decision GitHub itself doesn't treat as blocking.

Requirements

  • Accepting a staged merge action must re-verify the preconditions that justified staging it — not just that the head SHA is unchanged.
  • The re-check must cover mergeable state, CI conclusion, and review decision.
  • Must not depend on GitHub-side branch protection being configured, since that's optional per-repo.

Deliverables

  • Before calling executeAgentMaintenanceActions in decidePendingAgentAction, re-fetch live mergeable_state, CI conclusion, and reviewDecision, and treat a flip in any of them the same way a head-SHA mismatch is treated today (supersede/reject) rather than proceeding.
  • Prefer re-running planAgentMaintenanceActions (or an equivalent live disposition check) against fresh data at accept time, and only proceed if it still yields canMerge === true — this closes the gap structurally rather than accumulating ad hoc field checks, and protects the sibling approve/close action classes from the same staleness pattern.
  • Add a regression test in test/unit/agent-approval-queue.test.ts that stages a merge, then mutates the live/persisted PR to mergeableState: "dirty" (and separately: CI failing, reviewDecision CHANGES_REQUESTED) while holding the head SHA fixed, asserting accept is refused rather than executing.

Acceptance criteria

  • A staged merge whose mergeable state, CI, or review decision has changed since staging is rejected/superseded on accept, not executed.
  • A staged merge whose preconditions are still true at accept time executes normally (no regression to the existing head-SHA-freshness behavior).

Expected outcome

Approval-queue "accept" always reflects the PR's current mergeability, not a snapshot that may be hours or days stale — eliminating a real path to merging a PR that no longer meets its own staged requirements.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

Status
Done

Relationships

None yet

Development

No branches or pull requests

Issue actions