From 006ecbb91f72eb9d7d864bd691233bfa6d45f088 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 13 Jun 2026 15:10:14 -0700 Subject: [PATCH] fix(ci): resolve fork PRs when recording the preview deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Record deployment for Reviewbot" step resolved the PR via the commit→PR association API, with workflow_run.pull_requests as a fallback. BOTH are empty for fork PRs (the common case here — most open PRs originate from forks), so the step hit "Could not resolve an open PR for " and never recorded a Deployment — even though the preview itself deployed fine. With no Deployment/environment_url, Reviewbot's "after" screenshot stayed stuck on "Rendering preview…". Add a fork-safe fallback: scan open PRs for the one whose head.sha equals this build's GitHub-set head_sha (the base repo tracks the fork head as refs/pull/N/head, so head.sha is trustworthy even for forks). Add pull-requests:read for the pulls.list call. This is the second half of the preview fix; the first (allowing the served .zip asset through artifact validation) landed in the prior PR. --- .github/workflows/ui-preview-deploy.yml | 34 ++++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ui-preview-deploy.yml b/.github/workflows/ui-preview-deploy.yml index abd46ae96a..5414420b68 100644 --- a/.github/workflows/ui-preview-deploy.yml +++ b/.github/workflows/ui-preview-deploy.yml @@ -24,6 +24,7 @@ permissions: contents: read actions: read # download the build artifact from the triggering run deployments: write # record the preview Deployment Reviewbot reads + pull-requests: read # resolve the (often fork) PR for this build by matching head SHA concurrency: group: ui-preview-deploy-${{ github.event.workflow_run.head_sha }} @@ -187,16 +188,31 @@ jobs: const url = ${{ toJSON(steps.upload.outputs.preview_url) }}; // Trust only the GitHub-set head_sha — never fork-supplied data. const sha = context.payload.workflow_run.head_sha; - // Resolve the PR from the head commit. workflow_run.pull_requests is EMPTY for fork PRs, - // so look it up from the commit (the base repo holds the fork head as refs/pull/N/head). - const assoc = await github.rest.repos.listPullRequestsAssociatedWithCommit({ - owner: context.repo.owner, - repo: context.repo.repo, - commit_sha: sha, - }); const slug = `${context.repo.owner}/${context.repo.repo}`; - const open = assoc.data.find((p) => p.state === "open" && p.base.repo.full_name === slug); - const prNumber = open?.number ?? context.payload.workflow_run.pull_requests?.[0]?.number; + // Resolve the PR for this build. Both signals below are EMPTY for fork PRs (the common case + // here): workflow_run.pull_requests is empty for cross-repo runs, and the commit→PR + // association API doesn't index a fork-head commit from the base repo. So we ALSO match the + // open PR whose head points at this exact build SHA — head.sha is GitHub-set (the base repo + // tracks the fork head as refs/pull/N/head), so it's safe to trust even for forks. + let prNumber = context.payload.workflow_run.pull_requests?.[0]?.number; + if (!prNumber) { + const assoc = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: sha, + }); + prNumber = assoc.data.find((p) => p.state === "open" && p.base.repo.full_name === slug)?.number; + } + if (!prNumber) { + // Fork-safe fallback: scan open PRs for the one whose head is this build's commit. + const openPrs = await github.paginate(github.rest.pulls.list, { + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + per_page: 100, + }); + prNumber = openPrs.find((p) => p.head.sha === sha)?.number; + } if (!prNumber) { core.setFailed(`Could not resolve an open PR for ${sha} — skipping deployment record.`); return;