fix(webhook): catch a base retarget with no new commit, tighten event health, and stop label/issue churn from going stale - #9169
Merged
Conversation
… health, and stop label/issue churn from going stale #9055 — a contributor can move a green PR onto a new base after CI passed against the old one, with the head SHA unchanged. Nothing else notices: GitHub does not re-run pull_request workflows or emit a new head for a base-only edit, so the stored diff/patches, the AI-review cache (its fingerprint deliberately excludes baseSha), and the CI aggregate all keep describing the ABANDONED base -- permanently, since the sweep only re-syncs on head/label drift. classifyPullRequestFreshness gains an explicit `base_changed` reason, checked right after head/state and before the draft check, so a live base mismatch is caught at the last possible moment -- immediately before a merge/approve mutation -- using the SAME live fetch that already proves the head, no extra GitHub call. Threaded through AgentActionExecutionContext.expectedBaseRef from the stored PR row, exactly like expectedHeadSha already is. The webhook side: `pull_request.edited` with `changes.base` is GitHub's own signal that this specific mutation happened. It now forces a fresh file sync (bypassing the head-keyed `filesUpToDate` check that would otherwise skip the refetch since the head hasn't moved) and invalidates the PR-state, CI-state, and review caches -- treated as review-invalidating exactly like a new commit. #9058 — two related event-subscription gaps. `check_run`/`check_suite` were not required events, so an installation missing them reported "healthy" while silently degrading from event-driven to sweep-only for CI settlement -- despite that path being documented in-code as THE auto-merge / close-on-red trigger. `pull_request_review` had the same gap for the human-approval signal the merge gate reads. All three are now required, and the self-host setup wizard's manifest is derived from the same list instead of hand-maintained -- it had already drifted, omitting `issue_comment` and `repository`, so a wizard-created App started with every `@loopover …` command dead and no rename handling. #9059 — two bounded coverage gaps from the same audit. (a) Label/assignment churn on a linked issue never reached upsertIssueFromGitHub: maybeReReviewOnLinkedIssueChange returns true unconditionally once repo+installation+issue are present, and the caller short-circuits on that before handleIssueWebhookEvent -- the only other place the row gets upserted for an `issues` event. So `issues.labels_json` and assignee state only advanced on opened/edited/closed/reopened, or the (up to 6-hourly) backfill -- while label churn is the single most common issue mutation, read by issue-side advisories, slop triage, enrichment, and the MCP/API issue surfaces. The upsert now runs first, before the wake fan-out. (b) `pull_request.labeled`/`unlabeled` did a row re-sync but were never in PR_PUBLIC_SURFACE_ACTIONS, so a maintainer adding or removing the manual-review hold label was only picked up by the sweep -- unbounded lag under REST-budget backpressure. Both actions are disposition INPUTS (the hold reads straight off the PR's labels), so they now join edited/synchronize. Part (c) of #9059 (status/workflow_run never triggering a re-review) was already closed by #9116 -- verified via ciReReviewCoalesced/ reReviewStoredPullRequest already wired into maybeInvalidateCiCacheOnLegacyCiEvent. Targeted tests only (no full local gate run this pass, per instruction): the files this change touches plus the new coverage tests all pass locally (test/unit/{issue-label-churn-upsert,pr-base-retarget-webhook, pr-labeled-public-surface,pr-freshness,agent-action-executor, pending-closure-watchdog,pr-outcome-reconciler,queue-2}.test.ts -- 369 tests). CI runs the full gate on push. Closes #9055 Closes #9058 Closes #9059
This was referenced Jul 27, 2026
JSONbored
added a commit
that referenced
this pull request
Jul 27, 2026
…abels only (#9175) (#9176) #9169 added "labeled"/"unlabeled" to PR_PUBLIC_SURFACE_ACTIONS unconditionally so the manual-review hold re-syncs immediately, but that also made every unrelated label change (e.g. tagging a PR "bug") run the full public-surface publish pipeline, breaking the noisy-PR-event debounce. Now only a label matching one of the resolved disposition labels (manual-review, ready-to-merge, changes-requested, migration-collision, pending-closure) triggers the immediate path; anything else stays debounced to the sweep. Closes #9175
3 tasks
JSONbored
added a commit
that referenced
this pull request
Jul 27, 2026
…s with the #9169 required-events expansion (#9203) #9169 promoted pull_request_review/check_run/check_suite to REQUIRED_INSTALLATION_EVENTS and switched the self-host setup wizard's manifest to derive default_events from that same canonical list. Three test fixtures (one in api.test.ts, two in backfill.test.ts) still mocked GitHub's live installation payload with the pre-#9169 event set, so their live-refresh assertions incorrectly expected a healthy status with no missing events. The self-hosting-github-app.mdx docs page's "Events: ..." sentence was similarly left describing the old 7-event list instead of the now- canonical 12-event set, failing the docs/manifest parity test. Also fixes an unrelated branding-drift regression surfaced by the same full test:ci run: a comment in predicted-gate-agreement.ts (added in #9173) still said "gittensory" instead of "loopover".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#9055 — base retarget with no new commit
A contributor can move a green PR onto a new base after CI passed against the old one, with the head SHA unchanged. GitHub does not re-run
pull_requestworkflows or emit a new head for a base-only edit, so nothing else notices: the stored diff/patches, the AI-review cache (fingerprint deliberately excludesbaseSha), and the CI aggregate all keep describing the abandoned base — permanently, since the sweep only re-syncs on head/label drift.classifyPullRequestFreshnessgains an explicitbase_changedreason, checked right after head/state and before the draft check.AgentActionExecutionContext.expectedBaseReffrom the stored PR row, exactly likeexpectedHeadShaalready is.pull_request.editedwithchanges.basenow forces a fresh file sync (bypassing the head-keyedfilesUpToDatecheck) and invalidates the PR-state, CI-state, and review caches.#9058 — event-subscription gaps
check_run/check_suite/pull_request_revieware now required events — an installation missing them used to report "healthy" while silently degrading from event-driven to sweep-only for CI settlement (that path is documented in-code as the auto-merge / close-on-red trigger). The self-host setup wizard's manifest is now derived from the same required-events list instead of hand-maintained — it had drifted, omittingissue_commentandrepository.#9059 — two coverage gaps
upsertIssueFromGitHub— the row upsert now runs before the wake fan-out short-circuit.pull_request.labeled/unlabelednow joinPR_PUBLIC_SURFACE_ACTIONSso a maintainer's manual-review hold takes effect immediately instead of waiting on the sweep.Verification
Per instruction, no full local gate run this pass — targeted tests only. All pass locally:
test/unit/{issue-label-churn-upsert,pr-base-retarget-webhook,pr-labeled-public-surface,pr-freshness,agent-action-executor,pending-closure-watchdog,pr-outcome-reconciler,queue-2}.test.ts(369 tests). CI runs the full gate on push.Closes #9055
Closes #9058
Closes #9059