fix(ci): resolve fork PRs by open-PR head SHA, not commit association - #3922
Conversation
The fork-PR e2e lane never ran. `resolve` looked the PR up with `listPullRequestsAssociatedWithCommit` against the base repo, and that endpoint returns nothing for a fork PR head: the commit reaches cozystack/cozystack only as `refs/pull/<n>/head`, and the association index does not walk `refs/pull/*`. So every fork PR fail-closed on "expected exactly one open PR for <sha>, found 0", stamped a red required "E2E Tests" its author could not clear by pushing, and skipped publish/e2e/report. It resolves same-repo heads correctly, which is why the hole stayed invisible — same-repo PRs get "E2E Tests" from pull-requests.yaml and never enter this workflow. Of 935 runs, 930 skipped as designed and all 5 that reached `resolve` stopped there; three on `found 0`, and one of those reds was later overwritten by a mirrored same-repo run, which hid it again. Enumerate the base repo's open pull requests instead and match on the head SHA. Same trust properties as the call it replaces — plain base-repo API data the fork cannot forge — and `state: 'open'` at the source keeps the rule that a closed or merged PR never counts. The exactly-one guard and the head_sha match are unchanged, so a stacked lower PR still passes and two PRs genuinely at the same head still fail closed. Pagination stops being a corner case and becomes load-bearing: the repo has 180 open PRs, and the fork PR that surfaced this sits on page two. Verified against the live API for the failing case: PR #1982 at 984936f now resolves to exactly one match, with labels present so the full-e2e override still reads. actionlint, zizmor and `node --check` on the extracted script all pass. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe fork workflow now lists all open base-repository pull requests and matches them by exact head SHA. It proceeds only when exactly one open pull request points directly to the triggering SHA. ChangesFork PR resolution
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to This localized CI workflow change corrects fork pull-request resolution while preserving the existing validation rules; no actionable merge-blocking risk remains after normal checks and review. Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM. I confirmed the root cause against the live API rather than taking the description for it: repos/cozystack/cozystack/commits/<sha>/pulls returns 0 for the heads of open fork PRs #3918, #3856 and #3800, and the replacement resolves each of those same heads to exactly one open PR. labels is present in the list payload, so the full-e2e override still reads. The repo carries 179 open PRs right now, which is two pages, so the pagination really is load-bearing.
Downstream only reads pr.number and pr.labels, both of which the list payload has, and the trust story holds: owner/repo is context.repo, and HEAD_SHA is 40-hex validated before it reaches any of this. node --check on the script wrapped the way github-script wraps it is clean, and gate-concurrency-contract.bats is 8/8 on this branch.
One thing I would add rather than block on. The resolve lookup has never been under test, and that same bats file already greps this exact workflow for structural invariants, so it is the natural home for two asserts: the step lists open PRs with pagination, and it does not call listPullRequestsAssociatedWithCommit. As it stands, the only thing keeping the association call from coming back later as an optimization is a comment saying it should not, and greps of that shape are what the file exists for.
The part you flagged as unverified is the real residual: publish, e2e and report have never executed in this workflow, so the first fork PR through the gate exercises all three cold. Worth watching the digest push and the HEAD^2 == head_sha assert on that run.
Fork PR e2e lane never ran.
resolvelooks up PR for head sha withlistPullRequestsAssociatedWithCommiton base repo, and this endpoint returns nothing for fork PR head, because fork commit reaches base repo only asrefs/pull/<n>/headand association index doesn't walkrefs/pull/*. So every fork PR failed closed onexpected exactly one open PR for <sha>, found 0, got red requiredE2E Teststhat its author cant clear by pushing, andpublish,e2e,reportskipped.Endpoint works fine for same-repo heads, that is why nobody saw it - same-repo PRs get
E2E Testsfrompull-requests.yamland never enter this workflow at all. Of 935 runs ofE2E (fork)930 skipped as designed, and all 5 that reachedresolvestopped right there (three onfound 0, one on genuinely failed fork build). One of those reds was later overwritten by mirrored same-repo run, so symptom got hidden second time.Checked on live api: on base repo that endpoint returns 0 for head sha of all 5 currently open fork PRs and 1 for same-repo PRs, on fork repo it returns 1 for same commit. Listing open PRs of base repo and matching head sha returns 1.
Fix
Same trust properties as call it replaces, this is plain base-repo api data that fork cannot forge, and asking
state: 'open'at source keeps old rule that closed or merged PR never counts. Exactly-one guard and head sha match are not changed, so stacked lower PR whose head is only contained in upper branch still passes, and two PRs really sitting at same head still fail closed. Pagination becomes load bearing here, repo has 180 open PRs and fork PR that surfaced this sits on page two.Most of diff is the comment block, rewritten so association call doesn't come back later as an optimization.
Simulated new logic on live api, failing head resolves to exactly one open PR and
labelsare present in list payload, sofull-e2eoverride still reads. actionlint, zizmor,node --checkon extracted script and pre-commit pass.What is not verified
publish,e2eandreportnever executed in this workflow, so first fork PR going through the gate will exercise them for the first time. I would watch digest push from OCI archives andHEAD^2 == head_shaassert. Also there is no test harness for inlineresolvescript and this PR doesn't add one.Summary by CodeRabbit