Repository: vercel/next.js
Pull request: Only retry if canary fails on required jobs

We currently retry if optional jobs fail like rspack e.g. we sent a notification in https://github.com/vercel/next.js/actions/runs/17771261730/job/50507663875 because https://github.com/vercel/next.js/actions/runs/17769150549/attempts/5 failed with just rspack jobs and having a succesful "thank you, next".

This is noisy since these jobs would also not block PRs. The optional jobs are currently responsibility of partners and not the Next.js on-call.

The idea is that even if this new step fails or doesn't find the designated required job's conclusion, we still send a notification. Basically, this PR is an optimization when we do know only optional jobs failed. If the step is faulty, we still want to receive notifications just like before.

Changed files:
filename: .github/workflows/build_and_deploy.yml
patch: @@ -708,6 +708,7 @@ jobs:
   buildPassed:
     needs: ['deploy-target', 'build', 'build-wasm', 'build-native']
     if: ${{ always() && needs.deploy-target.outputs.value != '' }}
+    # Coupled with retry logic in retry_test.yml
     name: thank you, build
     runs-on: ubuntu-latest
     steps:

filename: .github/workflows/build_and_test.yml
patch: @@ -1052,6 +1052,7 @@ jobs:
 
     if: always()
     runs-on: ubuntu-latest
+    # Coupled with retry logic in retry_test.yml
     name: thank you, next
     steps:
       - run: exit 1

filename: .github/workflows/retry_test.yml
patch: @@ -2,6 +2,8 @@ name: retry-tests
 
 on:
   workflow_run:
+    # Make sure that required_job_conclusion knows what the name of the required
+    # job is in each workflow.
     workflows: ['build-and-test', 'build-and-deploy']
     branches: [canary]
     types:
@@ -25,7 +27,51 @@ jobs:
       }}
     runs-on: ubuntu-latest
     steps:
+      - name: Check conclusion of required job
+        id: required_job_conclusion
+        uses: actions/github-script@v7
+        continue-on-error: true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          script: |
+            // See build-and-test.yml and build-and-deploy.yml for the required job names
+            const requiredJobName = {
+              'build-and-test': 'thank you, next',
+              'build-and-deploy': 'thank you, build',
+            }[context.workflow]
+
+            async function rerunIfRequiredNotSuccessful() {
+              const result = await github.paginate.iterator(
+                'GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs',
+                {
+                  attempt_number: context.payload.workflow_run.run_attempt,
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  run_id: context.payload.workflow_run.id,
+                }
+              )
+
+              for await (const { data: jobs } of result) {
+                for (const job of jobs) {
+                  if (job.name === requiredJobName) {
+                    console.log(
+                      "Using conclusion '%s' from %s",
+                      job.conclusion,
+                      job.html_url
+                    )
+                    return job.conclusion
+                  }
+                }
+              }
+
+              console.log("Couldn't find job with name '%s'", requiredJobName)
+              return null
+            }
+
+            return await rerunIfRequiredNotSuccessful()
       - name: send retry request to GitHub API
+        if: ${{ steps.required_job_conclusion.outputs.result != 'success' }}
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |