pr-status: add --wait flag and background execution for /pr-status command (#90687)

## 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

---------

Co-authored-by: Claude <[email protected]>
This commit is contained in:
Tobias Koppers
2026-03-03 10:41:41 +01:00
committed by GitHub
co-authored by Claude
parent 11823f8471
commit cfd5d5db0f
2 changed files with 84 additions and 40 deletions
+14 -5
View File
@@ -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.
+70 -35
View File
@@ -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) => {