From 39276f80d070b8ad3663fa50ca4c5a97c50df453 Mon Sep 17 00:00:00 2001 From: Miranda Limonczenko Date: Thu, 23 Jul 2026 11:33:38 -0700 Subject: [PATCH] fix(docs ci): stop docs-e2e from polling the broken GitHub Deployments API (#48226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - `vercel/wait-for-deployment-action` in [docs-e2e.yml](.github/workflows/docs-e2e.yml) polls GitHub's Deployments API for a `Preview – docs` deployment, but Vercel's GitHub App has not written a GitHub Deployment object repo-wide since 2026-02-17 (broken app auth). The step times out after 900s on every PR that touches `apps/docs`, even though the preview build itself succeeds (`Vercel – docs` commit status is green). - Replace the wait step with a custom poll of the `Vercel – docs` commit status (which Vercel keeps posting correctly), then resolve the actual preview URL via Vercel's own deployments API (`GET /v13/deployments/{id}`) using the deployment ID embedded in the commit status's `target_url`, reusing the existing `VERCEL_TOKEN` / `VERCEL_TEAM_ID` secrets. - Drops the now-unused `deployments: read` permission. ## Context Reported in Slack: https://supabase.slack.com/archives/C023E4L60R3/p1784721725606599?thread_ts=1784658589.182079&cid=C023E4L60R3 (surfaced by [#48178](https://github.com/supabase/supabase/pull/48178) failing on this step — [run 29916797889](https://github.com/supabase/supabase/actions/runs/29916797889?pr=48178)). Agreed workaround from that thread: swap the wait step to poll the `Vercel – docs` commit status instead of the Deployments API. ## Test plan - [ ] Confirm this workflow run (triggered by this PR since it edits `apps/docs/**`... actually this PR only touches the workflow file, so verify via `workflow_dispatch` or a follow-up PR touching `apps/docs/**`) passes the "Wait for Vercel docs preview" step and resolves a working `deployment-url` - [ ] Confirm downstream Playwright E2E run against the resolved preview URL succeeds - [ ] Confirm the step still fails cleanly (clear error, no silent hang) if the Vercel deployment itself fails 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Summary by CodeRabbit * **Bug Fixes** * Improved documentation preview deployment handling in end-to-end tests. * Replaced the preview wait logic with more reliable polling for the relevant commit status, including clear success/failure/error and timeout behavior. * Resolve the correct documentation preview URL before tests proceed. * **Chores** * Tightened permissions for the documentation E2E workflow to use only the required access scopes. * Streamlined job setup steps so Node/Pnpm preparation runs earlier in the workflow. --------- Co-authored-by: Claude Sonnet 5 --- .github/workflows/docs-e2e.yml | 41 ++++++----- scripts/waitForVercelDocsPreview.js | 108 ++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 scripts/waitForVercelDocsPreview.js diff --git a/.github/workflows/docs-e2e.yml b/.github/workflows/docs-e2e.yml index eae790a43ed..f27fa3a407d 100644 --- a/.github/workflows/docs-e2e.yml +++ b/.github/workflows/docs-e2e.yml @@ -25,7 +25,6 @@ concurrency: permissions: contents: read - deployments: read statuses: read pull-requests: read @@ -45,8 +44,20 @@ jobs: persist-credentials: false sparse-checkout: | e2e/docs + scripts patches + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + name: Install pnpm + with: + run_install: false + + - name: Use Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' + # Vercel skips the docs preview when a PR only changes the harness # (e2e/docs, workflow). Wait for a preview only when apps/docs changed. - name: Detect docs app changes @@ -58,14 +69,21 @@ jobs: docs_app: - 'apps/docs/**' + # Vercel's GitHub App stopped writing GitHub Deployment objects on + # 2026-02-17 (broken app auth), so vercel/wait-for-deployment-action + # times out polling that API even though the preview builds fine. + # Poll the "Vercel – docs" commit status instead — Vercel keeps posting + # those — then resolve the deployment it points to via Vercel's own API + # to get the actual preview URL. See scripts/waitForVercelDocsPreview.js. - name: Wait for Vercel docs preview if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && steps.filter.outputs.docs_app == 'true' id: deployment - uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037 - with: - project-slug: docs - environment: preview - timeout: '900' + run: node scripts/waitForVercelDocsPreview.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }} - name: Resolve base URL id: base-url @@ -87,17 +105,6 @@ jobs: echo "use_bypass=false" >> "$GITHUB_OUTPUT" fi - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - name: Install pnpm - with: - run_install: false - - - name: Use Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version-file: '.nvmrc' - cache: 'pnpm' - - name: Install dependencies run: pnpm install --frozen-lockfile --filter=e2e-docs... diff --git a/scripts/waitForVercelDocsPreview.js b/scripts/waitForVercelDocsPreview.js new file mode 100644 index 00000000000..781d62370ba --- /dev/null +++ b/scripts/waitForVercelDocsPreview.js @@ -0,0 +1,108 @@ +// Vercel's GitHub App has stopped writing GitHub Deployment objects since +// 2026-02-17 (broken app auth), so polling the Deployments API (as +// vercel/wait-for-deployment-action does) times out even though the docs +// preview builds fine. Poll the "Vercel – docs" commit status instead, then +// resolve the actual preview URL via Vercel's own deployments API. +const { appendFileSync } = require('fs') + +const STATUS_CONTEXT = 'Vercel – docs' +const TIMEOUT_MS = 900_000 +const POLL_INTERVAL_MS = 15_000 + +async function fetchLatestStatus(repository, sha, githubToken) { + const url = `https://api.github.com/repos/${repository}/commits/${sha}/statuses` + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${githubToken}`, + Accept: 'application/vnd.github+json', + }, + }) + + if (!response.ok) { + throw new Error(`Failed to fetch commit statuses: ${response.status} ${response.statusText}`) + } + + const statuses = await response.json() + + return statuses + .filter((status) => status.context === STATUS_CONTEXT) + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0] +} + +async function resolveDeploymentUrl(targetUrl, vercelToken, teamId) { + const rawId = targetUrl.split('/').filter(Boolean).pop() + if (!rawId) { + throw new Error(`Could not parse a deployment ID from target_url: ${targetUrl}`) + } + const deploymentId = rawId.startsWith('dpl_') ? rawId : `dpl_${rawId}` + + const url = teamId + ? `https://api.vercel.com/v13/deployments/${deploymentId}?teamId=${teamId}` + : `https://api.vercel.com/v13/deployments/${deploymentId}` + + const response = await fetch(url, { + headers: { Authorization: `Bearer ${vercelToken}` }, + }) + + if (!response.ok) { + throw new Error( + `Failed to resolve Vercel deployment ${deploymentId}: ${response.status} ${response.statusText}` + ) + } + + const deployment = await response.json() + return `https://${deployment.url}` +} + +function writeOutput(name, value) { + const outputFile = process.env.GITHUB_OUTPUT + if (!outputFile) { + throw new Error('GITHUB_OUTPUT environment variable is required') + } + appendFileSync(outputFile, `${name}=${value}\n`) +} + +async function main() { + const repository = process.env.GITHUB_REPOSITORY + const sha = process.env.HEAD_SHA + const githubToken = process.env.GITHUB_TOKEN + const vercelToken = process.env.VERCEL_TOKEN + const teamId = process.env.VERCEL_TEAM_ID + + if (!repository) throw new Error('GITHUB_REPOSITORY environment variable is required') + if (!sha) throw new Error('HEAD_SHA environment variable is required') + if (!githubToken) throw new Error('GITHUB_TOKEN environment variable is required') + if (!vercelToken) throw new Error('VERCEL_TOKEN environment variable is required') + + const start = Date.now() + + for (;;) { + const latest = await fetchLatestStatus(repository, sha, githubToken) + + if (latest?.state === 'success') { + if (!latest.target_url) { + throw new Error( + 'Vercel docs commit status succeeded but had no target_url to resolve a deployment from' + ) + } + const deploymentUrl = await resolveDeploymentUrl(latest.target_url, vercelToken, teamId) + writeOutput('deployment-url', deploymentUrl) + return + } + + if (latest?.state === 'failure' || latest?.state === 'error') { + throw new Error(`Vercel docs deployment failed (commit status: ${latest.state})`) + } + + if (Date.now() - start > TIMEOUT_MS) { + throw new Error('Timed out after 900s waiting for the Vercel docs preview deployment') + } + + await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)) + } +} + +main().catch((error) => { + console.error('Fatal error:', error) + process.exit(1) +})