Skip to content

orb(webhook): retargeting a PR base re-uses the diff computed against the OLD base — stale CI, stale review, wrong merge #9055

Description

@JSONbored

A contributor can change a PR base after CI is green, and ORB will merge into the new base using the diff, review, and CI computed against the old one.

Chain (all CONFIRMED in code)

  1. PR opened head=S, base=A. Files synced; pull_request_detail_sync_state.head_sha = S, files_synced_at set. CI for S runs against A, green.
  2. Contributor retargets to Bpull_request.edited with changes.base. Head SHA unchanged.
  3. upsertPullRequestFromGitHub writes the new baseRef (src/db/repositories.ts ~458) — ORB knows, but nothing acts.
  4. edited is in PR_PUBLIC_SURFACE_ACTIONS (processors.ts ~686-692), so the full readiness → gate → auto-maintain pipeline runs.
  5. refreshPullRequestDetails is called without force (processors.ts ~6594), and filesUpToDate keys on head SHA only (backfill.ts ~2298) → GET /pulls/{n}/files is skipped. Stored paths and patches are still the diff vs A.
  6. The AI-review cache fingerprint hashes title + body + those stale patches — all unchanged → cache hit, pre-retarget review replayed (baseSha was deliberately removed from the fingerprint, ai-review-cache-input.ts ~19-28).
  7. CI aggregate is keyed by head SHA, and GitHub does not re-run pull_request workflows on a base change — so the checks from A remain the only checks.
  8. classifyPullRequestFreshness (src/github/pr-freshness.ts ~47-93) checks state, head SHA, draft, labels — never base.refcurrent. Live CI recheck → passedmerge into B.

Everything downstream is derived from the abandoned base: guardrail path matching, slop/manifest-policy evidence, screenshot gate, prMigrationFilenames (migration-collision detection!), and the published review. changes.base is not even modelled — src/types.ts ~363-369 types changes only for repository.name.from.

The divergence is permanent for that head: the sweep re-syncs only on head/label drift (processors.ts ~3653), so it persists until a new commit.

Related same-class hole: there is no push handler at all (grep finds only metric labels). A direct push to the base branch moves every open PR's merge-base with no sibling re-gate — maybeEnqueueSiblingRegateForMergedPr fires only on a PR merge. Base-branch deletion is likewise invisible.

Fix

  1. Cheapest, highest value: add expectedBaseRef to PullRequestFreshness/classifyPullRequestFreshness and thread it from AgentActionContext, so a merge planned against base A is denied when the live base is B. The payload already carries base.ref.
  2. Add base_ref to pull_request_detail_sync_state and include it in the filesUpToDate predicate.
  3. Detect action === "edited" with changes.base (add it to the type) → refreshPullRequestDetails(..., {force: true}) + invalidatePrStateCache + invalidateCiStateCache.
  4. Treat a base change as review-invalidating (re-open the one-shot window).

Acceptance

  • Retarget a green PR to a different base → ORB refuses to merge until files, CI, and review are recomputed against the new base.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.orbGittensory Orb related - maintainer self-hosting analytics.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions