Repository: vercel/next.js
Pull request: pr-status: add --wait flag and background execution for /pr-status command

## Summary

- Add a `--wait` flag to `scripts/pr-status.js` that writes a partial report with currently available CI results, then blocks on `gh run watch` until CI completes, and re-runs the full analysis to produce the final report
- Refactor `main()` into a reusable `runAnalysis()` function so it can be called twice (partial + final)
- Update the `/pr-status` command to run the script with `--wait` in the background, present the partial report immediately while CI is still running, then poll for completion with 5-minute timeouts

### How it works

1. `/pr-status` launches `node scripts/pr-status.js --wait` in the background (1-minute Bash timeout)
2. The script writes the initial report (with whatever jobs have completed so far) and prints `Output written to ...`
3. The command polls for that message using `TaskOutput` with 20-second timeouts, then reads `index.md` and analyzes the partial results
4. Meanwhile, the script blocks on `gh run watch` until CI finishes, then re-runs the full analysis
5. After presenting the partial analysis, the command polls for the background script to complete using `TaskOutput` with 5-minute timeouts (repeating if needed)
6. When it finishes, the command re-reads the final report and analyzes any newly failed jobs

### Files changed

- `scripts/pr-status.js` — Extracted `runAnalysis()` from `main()`, added `--wait` flag with `gh run watch` blocking and re-analysis
- `.claude/commands/pr-status.md` — Updated workflow to use `--wait` with background execution, poll for readiness (20s timeouts), and poll for final results (5-min timeouts)

## Test plan

- [ ] Run `node scripts/pr-status.js <completed-pr-number>` (no `--wait`) — should behave identically to before
- [ ] Run `node scripts/pr-status.js <completed-pr-number> --wait` — should produce report and exit immediately (no waiting needed since CI is done)
- [ ] Run `node scripts/pr-status.js --wait` on a branch with in-progress CI — should write partial report, block on `gh run watch`, then write final report
- [ ] Run `/pr-status` command — should show partial results, then update with final results after CI completes

Changed files:
filename: .claude/commands/pr-status.md
patch: @@ -13,21 +13,21 @@ Analyze PR status including CI failures and review comments.
 
 ## Instructions
 
-1. Run the script to fetch PR status data:
+1. Run the script with `--wait` in the background to fetch PR status data and wait for CI completion:
 
    ```bash
-   node scripts/pr-status.js $ARGUMENTS
+   node scripts/pr-status.js $ARGUMENTS --wait
    ```
 
-   This fetches workflow runs, failed jobs, logs, and PR review comments, then generates markdown files.
+   Use the `run_in_background` Bash parameter with a `timeout` of 60000 (1 minute). The script writes a partial report immediately with currently available results, then blocks on `gh run watch` until CI completes, and finally re-runs the full analysis to produce the final report. Remember the background task ID for step 10.
 
-2. Read the generated index file for a summary:
+2. Poll the background task output using `TaskOutput` with `block=true` and `timeout` of 20000 (20 seconds). Check if the output contains `Output written to` (this means the initial report is ready). If not yet, poll again. Then read the generated index file:
 
    ```bash
    # Read scripts/pr-status/index.md
    ```
 
-   The index shows failed jobs, PR reviews, and inline review comments with links to details.
+   The index shows failed jobs, PR reviews, and inline review comments with links to details. If CI is still in progress, some jobs may not have results yet — this is the partial report.
 
 3. Spawn parallel haiku subagents to analyze the failing jobs (limit to 3-4 to avoid rate limits)
    - **Agent prompt template** (copy-paste for each agent):
@@ -104,6 +104,15 @@ Analyze PR status including CI failures and review comments.
 
 9. The script automatically checks the last 3 main branch CI runs for known flaky tests. Check the **"Known Flaky Tests"** section in index.md and the `flaky-tests.json` file. Tests listed there also fail on main and are likely pre-existing flakes, not caused by the PR. Mark them as **FLAKY (pre-existing)** in your summary table. Use `--skip-flaky-check` to skip this step if it's too slow.
 
+10. After presenting the partial analysis, poll for the background script (from step 1) to complete by calling `TaskOutput` with `block=true` and `timeout` of 300000 (5 minutes). If the script completes:
+    - Re-read `scripts/pr-status/index.md` for the final report (CI has now finished)
+    - Compare with the partial report: identify any **newly failed** jobs that weren't in the earlier analysis
+    - Spawn haiku subagents to analyze the new failures (same template as step 3)
+    - Present an updated summary incorporating all final results
+    - If no new failures appeared, confirm that the partial results were the complete picture
+
+    If `TaskOutput` times out, the script is still waiting for CI. Poll again with another `TaskOutput` call (same 5-minute timeout) until the script finishes or you decide CI is taking too long. Inform the user that CI is still running and the current report is partial. They can re-run `/pr-status` later for the final results.
+
 - Do not try to fix these failures or address review comments without user confirmation.
 - If failures would require complex analysis and there are multiple problems, only do some basic analysis and point out that further investigation is needed and could be performed when requested.
 

filename: scripts/pr-status.js
patch: @@ -1204,38 +1204,11 @@ async function getFlakyTests(currentBranch, runsToCheck = 5) {
 // Main Function
 // ============================================================================
 
-async function main() {
-  // Dispatch subcommands
-  const subcommand = process.argv[2]
-
-  if (subcommand === 'reply-thread') {
-    const threadId = process.argv[3]
-    const body = process.argv[4]
-    if (!threadId || !body) {
-      console.error(
-        'Usage: node scripts/pr-status.js reply-thread <threadNodeId> <body>'
-      )
-      process.exit(1)
-    }
-    replyToThread(threadId, body)
-    return
-  }
-
-  if (subcommand === 'resolve-thread') {
-    const threadId = process.argv[3]
-    if (!threadId) {
-      console.error(
-        'Usage: node scripts/pr-status.js resolve-thread <threadNodeId>'
-      )
-      process.exit(1)
-    }
-    resolveThread(threadId)
-    return
-  }
-
-  // Parse CLI argument for PR number
-  const prNumberArg = subcommand
-
+/**
+ * Runs the full PR status analysis and writes output files.
+ * Returns { runId, isRunInProgress } so the caller can decide whether to wait.
+ */
+async function runAnalysis(prNumberArg, skipFlakyCheck) {
   // Step 1: Delete and recreate output directory
   console.log('Cleaning output directory...')
   await fs.rm(OUTPUT_DIR, { recursive: true, force: true })
@@ -1254,7 +1227,7 @@ async function main() {
 
   if (runs.length === 0) {
     console.log('No workflow runs found for this branch.')
-    process.exit(0)
+    return { runId: null, isRunInProgress: false }
   }
 
   // Find the most recent run (first in list)
@@ -1366,7 +1339,7 @@ async function main() {
         {}
       )
     )
-    process.exit(0)
+    return { runId: latestRun.id, isRunInProgress: false }
   }
 
   if (hasNoFailedJobs && hasInProgressOrQueued) {
@@ -1475,7 +1448,7 @@ async function main() {
 
   // Step 8: Check for known flaky tests across branches (skip with --skip-flaky-check)
   let flakyTests = new Set()
-  if (!process.argv.includes('--skip-flaky-check')) {
+  if (!skipFlakyCheck) {
     flakyTests = await getFlakyTests(branchInfo.branchName, 5)
     if (flakyTests.size > 0) {
       await fs.writeFile(
@@ -1505,6 +1478,68 @@ async function main() {
   await fs.writeFile(path.join(OUTPUT_DIR, 'index.md'), indexMd)
 
   console.log(`\nDone! Output written to ${OUTPUT_DIR}/index.md`)
+  return { runId: latestRun.id, isRunInProgress }
+}
+
+async function main() {
+  // Dispatch subcommands
+  const subcommand = process.argv[2]
+
+  if (subcommand === 'reply-thread') {
+    const threadId = process.argv[3]
+    const body = process.argv[4]
+    if (!threadId || !body) {
+      console.error(
+        'Usage: node scripts/pr-status.js reply-thread <threadNodeId> <body>'
+      )
+      process.exit(1)
+    }
+    replyToThread(threadId, body)
+    return
+  }
+
+  if (subcommand === 'resolve-thread') {
+    const threadId = process.argv[3]
+    if (!threadId) {
+      console.error(
+        'Usage: node scripts/pr-status.js resolve-thread <threadNodeId>'
+      )
+      process.exit(1)
+    }
+    resolveThread(threadId)
+    return
+  }
+
+  // Parse CLI arguments
+  const args = process.argv.slice(2)
+  const waitFlag = args.includes('--wait')
+  const skipFlakyCheck = args.includes('--skip-flaky-check')
+  const prNumberArg = args.find((a) => !a.startsWith('--'))
+
+  // Run the initial analysis
+  const { runId, isRunInProgress } = await runAnalysis(
+    prNumberArg,
+    skipFlakyCheck
+  )
+
+  if (!runId) {
+    process.exit(0)
+  }
+
+  // If --wait and CI is still running, wait for completion then re-run
+  if (waitFlag && isRunInProgress) {
+    console.log('\nWaiting for CI to complete (gh run watch)...')
+    try {
+      execSync(`gh run watch ${runId} --compact -R vercel/next.js`, {
+        stdio: 'inherit',
+      })
+    } catch {
+      // gh run watch exits non-zero when the run fails, which is expected
+    }
+
+    console.log('\nCI completed. Re-running analysis...')
+    await runAnalysis(prNumberArg, skipFlakyCheck)
+  }
 }
 
 main().catch((err) => {