feat(scripts): add pr-await CI gate to collapse PR polling into one call (#2873)

Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com>
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
nova28
2026-08-21 18:06:13 +08:00
committed by GitHub
parent e8e3a11262
commit c539cb8149
8 changed files with 1710 additions and 25 deletions
+15 -9
View File
@@ -16,15 +16,21 @@ Do not read source code, edit files, push, post or resolve GitHub comments,
trigger workflows, fetch full CI logs, or spawn subagents.
Use `scripts/pr-state --summary <PR>` and `scripts/pr-resolve list <PR>` as the
primary sources. Poll at a 30-second cadence for at most 20 minutes. In the
default mode, return early for a failed check, merge conflict, actionable
review feedback, or a terminal clean state. If the caller says "wait N
minutes" or "then fix up", use strict-deadline mode: calculate and include the
absolute deadline in the polling prompt, accumulate findings, and do not return
early for findings, pending checks, or a clean snapshot. Stop early only if the
PR is merged/closed or access is revoked. At the deadline, return the latest
named pending checks and named actionable review findings, not only aggregate
CI and review states.
primary sources. In the default mode, poll at a 60-second cadence for at most
20 minutes and return early for a failed check, merge conflict, actionable
review feedback, or a terminal clean state. Keep polling directly in this mode:
`scripts/pr-await` waits for every check to finish, which cannot return early on
a failure or conflict while other checks are still pending. If the caller says "wait N
minutes" or "then fix up", use strict-deadline mode instead: the 20-minute cap
does not apply there — honor the caller's own N-minute deadline. Those
semantics are exactly what `scripts/pr-await <PR> --deadline-min N` implements,
so prefer it there and report its single output. Without that script, still poll
at a 60-second cadence but for the caller's full N minutes: calculate and
include the absolute deadline in the polling prompt, accumulate findings, and
do not return early for findings, pending checks, or a clean snapshot. Stop
early only if the PR is merged/closed or access is revoked. At the deadline,
return the latest named pending checks and named actionable review findings,
not only aggregate CI and review states.
Before the first GitHub request, obtain any runtime network approval required by
the platform. A denied, cancelled, or interrupted approval is terminal: do not
+43 -15
View File
@@ -50,19 +50,45 @@ If the fresh mergeability query reports `mergeable=CONFLICTING` or
result, push it, and restart this section with a new PR-state snapshot. Do not
triage comments or checks against a conflicted head.
For pending CI, do not run a rapid polling loop. Wait at a reasonable interval,
then run the same summary again. Stop after about 20 minutes and report the
exact pending checks as "CI in progress." Use two monitoring modes: default
monitoring may return early for a failed check, merge conflict, actionable
finding, or clean terminal state; strict-deadline monitoring applies when the
user says "wait N minutes" or "then fix up." In strict mode, calculate and
pass an absolute deadline, accumulate failures/comments, and do not return
early for findings, pending checks, or a clean snapshot; stop early only when
the PR is merged/closed or access is revoked. Do not use interactive `gh pr
checks --watch` in the primary conversation: its TTY redraws make captured
output unusable. Use saved `scripts/pr-state --summary` snapshots about 60–90
seconds apart, or the read-only `pr-poller` only when the user explicitly asked
to wait or monitor.
For pending CI, wait with `scripts/pr-await <PR>`. It blocks below the
conversation and prints one report when every check is terminal, so a wait of
any length costs a single round-trip. Do not re-run `scripts/pr-state
--summary` on a timer instead: each snapshot is a separate model turn that
re-reads the whole context, and waiting is only about 6% of what such a turn
actually does.
Default `--mode all-terminal` is the cost-correct choice and returns only when
no check is pending, so every failure arrives in one report and is fixed in one
pass. It is also the correct choice for a reason unrelated to cost: a failed job
can be visible while its parent workflow is still running, so an early return
reports a partial failure set. Use `--mode first-failure` only when the user
wants the first failure interactively; it costs an extra full CI cycle per
failure. Pass `--deadline-min` for a user-stated limit; on exit 2 report the
named pending checks as "CI in progress."
Read its exit code rather than re-deriving state: 0 clean, 1 terminal with
findings, 2 deadline with checks still pending, 3 blocked. Exit 3 covers every
case where a clean answer cannot be trusted: the PR merged or closed, a workflow
needs approval, access was lost, the merge-state query failed, the snapshot
reported `errors` or a null unresolved-thread count, or the host toolchain
cannot run `pr-state` correctly. Never downgrade an exit 3 to "probably clean."
Exit 1 counts blocking reviews, not only threads: a current-head
`CHANGES_REQUESTED` review can exist with no unresolved thread attached.
Every report records the jq and bash versions it ran under. That is provenance,
not a gate: `pr-state` used to fail silently on jq 1.6 and on the bash 3.2 that
macOS ships as `/bin/bash`, both of which made a dirty PR look clean, and both
are fixed at the source. What guards against a degraded `pr-state` now is the
snapshot itself, which is stronger than any version check: a summary reporting
`errors`, one whose unresolved-thread count is null, and one that does not parse
are all treated as unknown rather than clean. A blocked report on an old
toolchain adds a note naming it as a possible cause. A push during the wait
restarts the gate against the new head and is reported.
Do not use interactive `gh pr checks --watch` in the primary conversation: its
TTY redraws make captured output unusable. Use the read-only `pr-poller` only
when the user explicitly asked to wait or monitor and `pr-await` is unavailable.
Treat a poller's unresolved/pending snapshot as provisional: it can predate a
primary-session push or thread resolution. Re-run `scripts/pr-state --summary
<PR>` at the current head before acting on it or declaring completion.
@@ -82,8 +108,10 @@ then re-run the summary and require jobs to materialize before polling. `gh run
approve` is not a valid command.
Treat the state as clean only when the current head has no failed or pending
checks, no merge conflict, no actionable review thread or issue comment, and
qualifying exact-head semantic evidence where PR delivery requires it.
checks, no merge conflict, no blocking review (an active `CHANGES_REQUESTED`
or a review blocked at the exact current head), no actionable review thread or
issue comment, and qualifying exact-head semantic evidence where PR delivery
requires it.
## 2. Fix CI Failures