Repository navigation
Read app-pinned required checks from the head's check runs (#176) - #195
Merged
Merged
Conversation
A required check that a ruleset (integration_id) or classic protection (app_id) pins to a GitHub App was matched through check.app.databaseId in gh's statusCheckRollup. gh 2.102.0 never asks GitHub for a run's app, so the check always read as missing, even when it passed. When any required check is pinned to an app, the inspection now also reads the check runs of the exact head the PR read returned, through gh api graphql: each check suite with its app's databaseId and the suite's latest run of each name. A pinned check matches only runs of its name from a suite of its app; a commit status, or a run whose suite GitHub names no app for, never matches it. Checks any app may report are still read from the rollup alone. The read fails closed and keeps its reason: a gh failure, errors or malformed fields, another commit, a suite with more than 100 runs, more than 1,000 suites, or suites that change between pages fail the inspection; a deadline keeps the inspection's timeout. The test fakes no longer give rollup entries an app, and answer the new read in the shape gh 2.102.0 prints. The vitejs/vite and sveltejs/svelte recordings were made again at the same heads with the new read: vite's three checks pinned to GitHub Actions now read as passing, and svelte's pinned check reads as missing because GitHub names no app for its suite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 1 of the independent review of PR #195 found that the read of every check run on the head, with each suite's app, misread real repositories: - A private app's suite has `app: null` in GraphQL, so its runs never met the check pinned to it (sveltejs/svelte's vite-ecosystem-ci). - A suite with more than 100 runs failed the whole inspection, though the pinned check was elsewhere (microsoft/vscode has one with 101). The read now asks GitHub, per pinned check, for the suites of its app (checkSuites filterBy appId, which also returns a private app's suite) and, in each, the latest run of its name (checkRuns filterBy checkName, checkType LATEST). Each check is one alias; up to 25 go in one call, and a check whose app has more than 100 suites is paged up to 10 pages. The read still fails closed on any overflow left in that narrowed query. The read is skipped when the rules are unknown, so it cannot replace that blocker. When stages stop each other, the inspection now reports the first stage's failure, even when a stopped read then ends with a gh exit of its own. Tests: the fake answers the new query as GitHub does, filtering by app and name; the exit-0 GraphQL error case is labelled defensive. New: several checks per call, more checks than one call holds, paging to exactly the page limit, a pinned check with the rules unknown, and a stop-reason case with a pinned check. The vite and svelte recordings were made again (svelte now reads as passing), and new recordings show a re-run inside one suite (codeboost PR 151), a suite with 101 runs (vscode PR 343099) and the private app's suite in GraphQL and REST. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…review) Round 2 of the independent review of PR #195 found five low issues: - The fakes in inspectPinned, the stop-reason test and requiredTestState now answer as gh 2.102.0 does: the protection read's 404 as a CommandFailed with GitHub's body on stdout, the branch read with its protection, and a GraphQL error with GitHub's response on stdout. Each notes the fields it leaves out and that the gateway does not read them. - scripts/record-pinned-gh.ts remakes pinned-rerun, pinned-large-suite and private-app-suites, and recorded.ts says which script writes which files and what each holds. Remade today, vscode's suite now has 116 runs (101 earlier); the test checks the new count. - The docs and the gateway's comment say the inspection's deadline, not the 10-page cap, is the real limit on paging: a head with more suites than fit fails closed with the inspection's timeout. - guarded-merge.md's long Gate paragraph is split, and the pinned-check read has its own short section. The T12 evidence names the removed test as removed and points to its replacement; build step 4's status lines are left for the refresh after the parallel PRs merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 21, 2026
… out (#176 review) Round 3 of the independent review of PR #195 found that the code comment, the stop-reason test and guarded-merge.md described a stopped read that exits with its own error after the stop. runWithInput cannot give that: once it sees the abort before gh exits, it rejects with the abort's reason. The real case is a gh that had already exited while its pipes closed after the stop. All three now say so, and the test's fake models that order. It still fails without the first-failure line. The new fakes now use gh 2.102.0's shapes: the 401 and 404 bodies with documentation_url and runWithInput's stdout suffix, the unprotected branch read with its protection (UNPROTECTED_BRANCH), and the abort's reason for a stopped read. Notes name what the fakes leave out. guarded-merge.md says the fresh inspection's deadline fits a few pages and the 12 s display inspection about nine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rmission gap (#176 review round 4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iew round 5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… incomplete too (#176 review round 6) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilure claim (#176 review round 7) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… round 8) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
No blocking defects were identified, and regression coverage supports the matching and fail-closed behavior.
No open issues 🎉
What changed in this PR
Fixes #176 by reading app-pinned required checks from the exact PR head, enabling guarded merges when those checks pass.
Changes:
- Adds filtered, bounded GraphQL reads while preserving fail-closed behavior.
- Covers app matching, pagination, reruns, timeouts, and failure ordering.
- Updates recordings, recording scripts, and implementation documentation.
| File | Description |
|---|---|
| test/merge.test.ts | Tests pinned-check matching and failure handling. |
| test/merge-stop-reasons.test.ts | Tests preservation of the first stage failure. |
| test/merge-recorded.test.ts | Verifies behavior against recorded GitHub responses. |
| test/fixtures/gh/recorded.ts | Clarifies recording provenance. |
| test/fixtures/gh/private-app-suites.json | Records private-app visibility differences. |
| test/fixtures/gh/pinned-rerun.json | Records latest-run filtering after a rerun. |
| test/fixtures/gh/pinned-large-suite.json | Records filtering within a large suite. |
| test/fixtures/gh/nonadmin-ruleset.json | Adds pinned-check responses for Vite. |
| test/fixtures/gh/nonadmin-mixed.json | Adds private-app check responses for Svelte. |
| scripts/record-pinned-gh.ts | Adds reproducible pinned-check recordings. |
| scripts/record-merge-gh.ts | Updates the recording scope comment. |
| github/merge.ts | Reads pinned checks and preserves failure ordering. |
| docs/implementation/merge-handoff.md | Documents pinned-check behavior during merge jobs. |
| docs/implementation/guarded-merge.md | Explains filtering, limits, and failure behavior. |
| docs/designs/codeboost-plan-indexed-review.md | Updates T12 evidence for the fix. |
Review effort: Balanced
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.
Closes #176. Part of closing build step 4.
Problem
A required check pinned to a GitHub App (ruleset
integration_id, classicapp_id) always read asmissing:gh pr view --json statusCheckRollup(gh 2.102.0) never returns a check run's app, and the test fake returned anappfield real gh does not send.Change
github/merge.tsasks GitHub for exactly what each pinned check needs, at the exact head, throughgh api graphql:checkSuites(filterBy:{appId})thencheckRuns(filterBy:{checkName, checkType:LATEST}), one alias per pinned check, up to 25 per call; only an app with more than 100 suites is paged (up to 10 pages). The app filter also finds a private app's suite (GraphQL shows itsappas null). The name filter is exact.failed.signal.reason), as theinspectcomment says.nonadmin-ruleset(vite) andnonadmin-mixed(svelte, whose private-app pinned check now reads success) re-recorded at the same heads. New recordings:private-app-suites(svelte suites via GraphQL and REST),pinned-rerun(a real re-run, LATEST drops the failed run),pinned-large-suite(a vscode suite with more than 100 runs: 116 when last recorded).guarded-merge.md,merge-handoff.md, design T12 note.Validation
Validated head: f622c5e (CI and local gates below ran on it).
testpassed (run 39539638476);real-dockerpassed (run 39539638522).vitest run --project parallel) on f622c5e: 2501 passed, 1 skipped, 0 failed.merge.tsfails 33 tests including the svelte, re-run and vscode recordings (5ec3b0c); the old cursor/page order fails the page-10 test (4f88972).Historical counts (intermediate checkpoints, superseded by the above): 455 merge-suite tests at 75efb01; 477 at 5ec3b0c, 429d9c2 and 40717e1; full suite 2497 passed at 5ec3b0c.
Not proven: real paging past 100 suites of one app, more than 100 latest runs of one name in a suite, more than 25 pinned checks (fakes only); whether an app's commit status meets a pinned check (fails closed); a token without the Checks permission (fails closed). The re-run and vscode recordings come from repos whose rules pin nothing, so their rule and PR answers are faked.
Review record
app: null). Fixed with the server-side app filter; svelte now reads success.inspectPinnedfakes did not match gh. Now real 404 shape and branch fields; version and omissions noted.requiredTestStatefakes left out output silently. Real stdout or a note; the "Something went wrong" text is labelled not reproduced live.RecordedGhcomment wrong for the three new fixtures, and their script uncommitted. Newscripts/record-pinned-gh.ts; comment fixed. Re-recording grew the vscode suite from 101 to 116 runs; the test now checks 116.runWithInputcannot produce. Reworded (gh exited, pipes closed after the stop); fake models that order; the test still fails without the first-failure line.runWithInput(stderr, newline, stdout; bodies withdocumentation_url),UNPROTECTED_BRANCHfrom the recording, the abort's reason on timeout; one shared note names gh 2.102.0 and what the fakes leave out.mergeabledid. Noted.app, which gh never sends. Removed; noappfield is left in any rollup fake.Before Copilot (head f622c5e, validated head for this request)
agents/only, no overlap); not merged in, so the reviewed pair stands.Copilot round 1 (head f622c5e)
Copilot (copilot-pull-request-reviewer): "Approval recommended", no open issues. No change made.
Review-lesson audit (merge gate)
No new rule is needed; each finding is covered by an existing rule or is a one-off.
inspectPinned, stop-reasons andrequiredTestStatefakes), round 3 L2 (fakes leaving out output), round 7 L1 (the last rollupappfield). Also the issue's own cause (theappfield the merge fake returned).app; filter on the server instead), round 1 M2 (suites with more than 100 runs in the wild; filter by name), round 1 L1 (latency of the extra call), rounds 5–6 nits (the missing-cursor reason and its order against the page limit).🤖 Generated with Claude Code