Skip to content

fix(ci): resolve fork PRs by open-PR head SHA, not commit association - #3922

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/fork-e2e-pr-lookup
Aug 20, 2026
Merged

fix(ci): resolve fork PRs by open-PR head SHA, not commit association#3922
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/fork-e2e-pr-lookup

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fork PR e2e lane never ran. resolve looks up PR for head sha with listPullRequestsAssociatedWithCommit on base repo, and this endpoint returns nothing for fork PR head, because fork commit reaches base repo only as refs/pull/<n>/head and association index doesn't walk refs/pull/*. So every fork PR failed closed on expected exactly one open PR for <sha>, found 0, got red required E2E Tests that its author cant clear by pushing, and publish, e2e, report skipped.

Endpoint works fine for same-repo heads, that is why nobody saw it - same-repo PRs get E2E Tests from pull-requests.yaml and never enter this workflow at all. Of 935 runs of E2E (fork) 930 skipped as designed, and all 5 that reached resolve stopped right there (three on found 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

const openPrs = await github.paginate(
  github.rest.pulls.list,
  { owner, repo, state: 'open', per_page: 100 },
);
const open = openPrs.filter(p => p.head && p.head.sha === HEAD_SHA);

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 labels are present in list payload, so full-e2e override still reads. actionlint, zizmor, node --check on extracted script and pre-commit pass.

What is not verified

publish, e2e and report never 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 and HEAD^2 == head_sha assert. Also there is no test harness for inline resolve script and this PR doesn't add one.

Summary by CodeRabbit

  • Bug Fixes
    • Improved fork pull request detection by matching the exact triggering commit.
    • Prevented stacked or unrelated pull requests containing the same commit from being incorrectly selected.
    • Ensured the workflow proceeds only when exactly one matching open pull request is found.

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>
@github-actions github-actions Bot added the size/M This PR changes 30-99 lines, ignoring generated files label Aug 20, 2026
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d1521976-fa3a-4d50-9081-7075747923be

📥 Commits

Reviewing files that changed from the base of the PR and between 68d83cb and 7ac2ffe.

📒 Files selected for processing (1)
  • .github/workflows/e2e-fork.yaml

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The 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.

Changes

Fork PR resolution

Layer / File(s) Summary
Open PR enumeration and exact SHA filtering
.github/workflows/e2e-fork.yaml
The workflow uses paginated open pull request listing instead of commit association lookup. It filters results by head.sha === HEAD_SHA and fails closed unless exactly one match exists.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 7ac2f

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: kind/bug, area/ci, full-e2e

Suggested reviewers: lexfrei, lllamnyp

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main CI change: resolving fork PRs by matching open PR head SHAs instead of commit associations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fork-e2e-pr-lookup

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added area/ci Issues or PRs related to CI workflows, GitHub Actions, automation kind/bug Categorizes issue or PR as related to a bug labels Aug 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 1592fd9 into main Aug 20, 2026
17 of 19 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/fork-e2e-pr-lookup branch August 20, 2026 11:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci Issues or PRs related to CI workflows, GitHub Actions, automation kind/bug Categorizes issue or PR as related to a bug size/M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants