You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Promote the Held-Wait Exit 45 for Missing Required Checks to Main - #3484
Promotes the pull requests merged into develop since release 2.0.1184.
#3483: a held wait exits 45 with status=CHECKS_MISSING where it used to exit 0 over checks that never ran. The cases are a payload with no commit matching the head, a DIRTY merge with no check posted, and a DIRTY or BEHIND merge whose required context is unregistered or came from another app. The wait help and scripts/README.md name each exception that stays at 0.
#3485: the exit-0 list in the help and README names a pull request no longer open, which this promotion's own review found missing.
#3486: where the head's rollup holds a type the reader cannot parse, the CHECKS_MISSING clause and the BLOCKED hold's note say a required check may sit in that unread part, which this promotion's second review found missing.
#3490: the no-check-posted cause of exit 45 is scoped to DIRTY in the help and README and names its remedy, which this promotion's third review found missing.
#3491: the wait help and README restore the caveat that a review-present exit 0 does not poll the checks, which this promotion's fourth review found dropped.
attested local pass at the merged head, base develop. A Copilot round on the earlier head ee530a4b opened three findings, each fixed in 995ce78a and resolved, so no Copilot round covers the merged head
attested local pass at the merged head, base develop. A Copilot round on the earlier head b8a74e8a opened three findings, each fixed later on the branch and resolved, so no Copilot round covers the merged head
attested local pass at the merged head, base develop, and a Copilot table on the merged head naming exactly its 4 changed files. Its headline's test-wording preference was declined in a comment
attested local pass at the merged head, base develop, and a Copilot table on the merged head naming exactly its 2 changed files, verdict clean, no open findings
…Ran (#3483)
## Summary
A held `wait` exited `0` once nothing held the poll, even where the
head's required checks never ran: a `DIRTY` merge, a `BEHIND` merge with
a required context unregistered, and a payload whose rollup commit is
not the head. The maintainer decided on #3471 that these paths get a new
exit code naming that state.
- `wait` now exits `45` with `status=CHECKS_MISSING` on an open,
attested held head the poll released, under a merge other than `CLEAN`,
`UNSTABLE`, or `HAS_HOOKS`, where the payload carries no commit matching
the head or no check has posted.
- It also exits `45` where the merge reads `DIRTY` or `BEHIND` and a
context the base branch's rules require is missing from a rollup read
whole, or came from another app.
- A `DRAFT` merge, unread rules, and a `CHECKS TRUNCATED` rollup stay at
exit `0`, and a pull request no longer open never exits `45`. The help
and README name each exception.
- Each cause in the status line carries its own remedy.
- `pr_review_checks.py` gains `checks_never_ran`,
`required_unregistered`, which replaces `required_unmet` and now guards
the `BLOCKED` hold and `_unmet_note` alike, and `MERGE_UNGATED`.
`required_for` takes the merge states it reads rules for, and reads none
for a pull request no longer open.
- The `wait` help, `scripts/README.md`'s exit table, and its held-wait
paragraph are rewritten together.
Part of #3471, which the promotion pull request closes.
## Local pass findings
Every `introduced` finding the local passes raised is fixed on this
branch, the earlier list in this body included. These were declined as
`style` on text this change wrote, a preference between defensible forms
per fleet-code-review's rule to omit preferences:
- The exit `45` decision living partly in `required_for`'s `gated`
argument rather than wholly in `checks_never_ran`. Keeping it in the
caller is what limits the rules read to `DIRTY` and `BEHIND`.
- `gated` naming the merges whose rules are read, where the prose calls
any merge outside `MERGE_UNGATED` gated.
- The rules read running before the unreadable or empty-rollup cause
decides the outcome. The read is memoized, and its note is accurate
there.
- The help's exit-0 sentence on where a required check can stay
unregistered reading as wider than the attested held head. Its "then"
ties it to the attested clause before it.
- The unreadable-payload string running past 100 characters. No gate
enforces the width on a string literal.
## Test plan
- `python -m unittest test_pr_review test_pr_review_size
test_pr_review_copilot` from `tests/`: 534 tests pass.
- Mutations removing the open-state guard, or reading the rules ahead of
the attestation guard, each fail a test.
- The pre-commit gates (ruff, mypy, prose, eol) pass, and `prose_lint.py
--check sentence-length` is clean over the diff.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.04%. Comparing base (0da95bc) to head (f87bf62). ⚠️ Report is 422 commits behind head on main.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The change alters exit codes that merge-gating automation reads, and the description says no Copilot round covers the merged head of a promotion into the default branch.
This promotion carries pull request #3483 from develop to main. It closes #3471, where a held wait exited 0 under a DIRTY or BEHIND merge whose required checks never ran. The wait now exits 45 with status=CHECKS_MISSING for those cases, and the help and README describe each exception that still exits 0.
Changes:
wait exits 45 on an open, attested held head that nothing holds, when its rollup is unreadable or empty, or when a DIRTY or BEHIND merge lacks a required check or has it from another app.
pr_review_checks.py adds checks_never_ran and MERGE_UNGATED, and replaces required_unmet with required_unregistered, which reads a rollup only when it was read whole.
required_for takes the merge states whose rules it reads, and returns nothing for a pull request that is no longer open. The help text, scripts/README.md, and tests are updated to match.
File
Description
tests/test_pr_review.py
Adds and updates tests for exit 45, the open-state guard, the rules read limits, and the cut or unread rollup cases.
scripts/README.md
Adds exit 45 to the exit table and rewrites the held-wait paragraph to match the new behavior.
scripts/pr_review.py
Adds exit 45 to the wait help and the held-read logic, and changes required_for to take the merge states it reads.
scripts/pr_review_checks.py
Adds the checks_never_ran and required_unregistered helpers and the MERGE_UNGATED set, and updates the held-check logic to use them.
…3485)
## Summary
The promotion review of #3484 found the `wait` help and
`scripts/README.md` saying exit `0` on an attested held head can leave a
required check unregistered only under unread rules, a cut rollup, or a
`DRAFT` merge. A pull request no longer open can too, so the list now
names it in both places, replacing the separate sentence that said such
a pull request never exits `45`. Behavior is unchanged.
Part of #3471, which the promotion pull request closes.
## Pre-existing findings, not filed
- `scripts/README.md`, the held-wait paragraph (`pre-existing`,
`style`): "a cut rollup" where the help says `CHECKS TRUNCATED`, and
"then" following the `DIRTY` and `BEHIND` sentence, both wording the
earlier text already carried.
## Test plan
- `prose_lint.py --check sentence-length` is clean over the diff, and
the pre-commit gates pass.
- Two local passes checked the list against `checks_never_ran`,
`required_unregistered`, `required_for`, and the end of `wait`, and
found it complete.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
It changes a fleet gate's exit contract, and one moderate bug in the new exit 45 path is still unresolved.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Explain unread rollup nodes instead of reporting checks as unregistered
scripts/pr_review_checks.py:318
A required check can be reported as unregistered when the rollup holds a context type this cannot read, because required_unregistered drops unreadable nodes before matching. Under DIRTY or BEHIND the exit then says the check never registered and points at the head or the rule, although the digest says the rollup is partly unread. When an unread node is present, say so and point the maintainer at the head commit's contexts instead. The BLOCKED hold keeps its current behavior.
## Summary
The promotion review of #3484 found exit `45` saying a required check
had not registered while the head's rollup holds a type the reader
drops, which the digest names `CHECKS PARTIALLY UNREAD`. The required
check could sit there.
- Where the rollup is partly unread, the `CHECKS_MISSING` clause names
"sits in the rollup's unread part" as one more possible cause, and its
remedy leads with reading the head commit's checks and statuses. The
other causes and remedies are kept.
- The `BLOCKED` hold's `CHECKS_PENDING` note says an unmet required
check may sit in the unread part, on the same condition.
- The `wait` help and `scripts/README.md` say so for both, and the
help's hold precondition names the head's own rollup read and not
`CHECKS TRUNCATED`.
- Exit codes are unchanged. GitHub's rollup union holds only `CheckRun`
and `StatusContext` today, so this is reachable only after an API
change.
Part of #3471, which the promotion pull request closes.
## Local pass findings
Every `introduced` finding the local passes raised is fixed on this
branch, the earlier list in this body included.
Pre-existing findings, filed:
- `scripts/pr_review_checks.py`, the empty-rollup checks
(`pre-existing`): a rollup of only unreadable nodes is not read as
empty. Filed as #3488.
Declined as `style` on text this change wrote, a preference between
defensible forms per fleet-code-review's rule to omit preferences:
- `_unmet_note` with several unmet contexts reads "A, B, which may sit
in the rollup's unread part", where "which" can attach to the last name
alone.
- The help, README, and digest each name the unreadable type in slightly
different words.
## Test plan
- `python -m unittest test_pr_review test_pr_review_size
test_pr_review_copilot` from `tests/`: 534 tests pass, with positive and
negative cases for each conditional clause and remedy.
- The pre-commit gates pass, and `prose_lint.py --check sentence-length`
is clean over the diff.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Answering the previously missed finding "Explain unread rollup nodes instead of reporting checks as unregistered" at scripts/pr_review_checks.py:318: fixed in 6a1990f via #3486. Where the rollup holds a type the reader cannot parse, the CHECKS_MISSING clause names "sits in the rollup's unread part" as one more cause and its remedy leads with reading the head commit's checks and statuses, and the BLOCKED hold's CHECKS_PENDING note says the unmet check may sit there too. A rollup of only unreadable nodes is filed separately as #3488.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
It changes the exit codes a fleet merge gate reads, so a person should confirm the new exit-45 contract and its exceptions before approval.
No open issues 🎉
3 previously missed issues
In code that hasn't changed since last review
Qualify exit 45 as applying to DIRTY merges
scripts/pr_review.py:150
The exit 45 entry says an empty rollup releases on any merge other than CLEAN, UNSTABLE, or HAS_HOOKS. The code holds BLOCKED, BEHIND, and DRAFT at 30, so only a DIRTY merge with no check posted exits 45. Qualify the cause with "on a DIRTY merge". Same issue in scripts/README.md line 208, whose exit 45 row needs the same qualifier.
Explain and resolve the no-check conflict before running checks
scripts/pr_review_checks.py:314
The "no check has posted" cause ends on "until resolved" and gives no remedy, unlike the other causes. Name the conflict and the action, so the reader knows what to resolve before the head can run its checks. Same finding raised by a second reviewer, merged here.
Skip empty-reason assertion cases
tests/test_pr_review.py:3745
assertIn(reason, ...) always passes when reason is "", and four cases in this table use "". For those cases the output text is never checked. Guard the assertion so it runs only where a reason is expected.
## Summary
The third promotion review of #3484 raised three findings, each fixed
here:
- The `wait` help's exit 0 paragraph and `45` entry, and the README's
`45` row, offered "no check posted" for any merge other than `CLEAN`,
`UNSTABLE`, or `HAS_HOOKS`. The empty-rollup hold keeps `BLOCKED`,
`BEHIND`, and `DRAFT` at `30`, so each now scopes that cause to `DIRTY`.
- The no-check-posted cause named no remedy. It now says to resolve the
head's conflict with its base, and a test pins it.
- The read-shape test table asserted `assertIn("", ...)` on its exit 0
rows, which always passes. It now checks a reason only where one is
expected, and those rows stay covered by their `CHECKS_MISSING` absence
check.
Behavior and exit codes are unchanged.
Part of #3471, which the promotion pull request closes.
## Test plan
- `python -m unittest test_pr_review test_pr_review_size
test_pr_review_copilot` from `tests/`: 534 tests pass.
- The pre-commit gates pass, and `prose_lint.py --check sentence-length`
is clean over the diff.
- The recorded local pass traced the cause's reach to `DIRTY` alone and
found no finding.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Answering the three previously missed findings of the third round, each fixed in 578a265 via #3490:
"Qualify exit 45 as applying to DIRTY merges" (scripts/pr_review.py:150, scripts/README.md:208): the help's exit 0 paragraph and 45 entry and the README's 45 row now scope the no-check-posted cause to DIRTY.
"Explain and resolve the no-check conflict before running checks" (scripts/pr_review_checks.py:314): the cause now says to resolve the head's conflict with its base, and a test pins that remedy.
"Skip empty-reason assertion cases" (tests/test_pr_review.py:3745): the table asserts a reason only where one is expected, and its exit 0 rows stay covered by their CHECKS_MISSING absence check.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
It changes the exit codes of a merge gate that fleet automation reads and promotes that change to main, so it needs a final human review.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Restore exit-0 caveat for checks-not-run behavior
scripts/pr_review.py:125
The help dropped the general caveat that exit 0 does not prove the required checks ran. Exit 0 on a review present reads no rules and no check, so the sentence now implies a proof it does not give. The same gap is in scripts/README.md:202, whose exit 0 row needs the caveat restored too.
…cks (#3491)
## Summary
The fourth promotion review of #3484 found the rewrite of the `wait`
help's exit 0 text dropping its caveat that exit 0 does not prove the
required checks ran.
- The help now says, right after its opening exit 0 line, that a
review-present exit 0 does not poll the checks, so it does not prove the
required checks ran. The README's exit 0 row says the same.
- The help's claim that every workflow run on the head has completed is
scoped to an open attested held head, the one case that polls its runs.
Behavior is unchanged.
Part of #3471, which the promotion pull request closes.
## Pre-existing findings, not filed
- `scripts/pr_review.py` help, the exit 0 "only under" sentence
(`pre-existing`): its subject is "Exit 0", tied to the attested held
head only by "then", so read alone it reads as covering a review-present
exit 0 too.
## Dropped findings
- `scripts/README.md`, the exit 0 row (`style`): "on that review"
reaches back past the held-head clause.
- `scripts/pr_review.py` help (`style`): "held head whose commit the
payload carries" reads the commit as separate from the head.
## Test plan
- `prose_lint.py --check sentence-length` is clean over the diff, and
the pre-commit gates pass.
- The recorded local pass checked each changed sentence against the end
of `wait` and found it true.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Answering the fourth round's previously missed finding "Restore exit-0 caveat for checks-not-run behavior" (scripts/pr_review.py:125, scripts/README.md:202): fixed in f87bf62 via #3491. The help now says, right after its opening exit 0 line, that a review-present exit 0 does not poll the checks, so it does not prove the required checks ran, and the README's exit 0 row says the same. The help's completed-runs claim is scoped to an open attested held head, the one case that polls its runs.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
It adds a new wait exit code that downstream automation reads on a promotion to main, and nothing in this review shows that change is safe to approve without a maintainer.
Answering the fifth round's headline, "It adds a new wait exit code that downstream automation reads on a promotion to main, and nothing in this review shows that change is safe to approve without a maintainer": the new exit 45 is the maintainer's own decision, recorded on #3471 on 2026-10-25, and merging this promotion stays the maintainer's explicit go-ahead. Within the repository, no skill, governance file, or doc maps wait's held-head exit codes, which each feature pull request's local pass searched for, and every earlier exit keeps its meaning. A held wait that used to exit 0 over checks that never ran now exits 45 with status=CHECKS_MISSING, so a caller reading only 0 sees a non-zero exit where it previously saw a false pass. The five previously missed findings listed in this round were each answered in the comments above.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promotes the pull requests merged into develop since release 2.0.1184.
#3483: a held
waitexits45withstatus=CHECKS_MISSINGwhere it used to exit0over checks that never ran. The cases are a payload with no commit matching the head, aDIRTYmerge with no check posted, and aDIRTYorBEHINDmerge whose required context is unregistered or came from another app. Thewaithelp andscripts/README.mdname each exception that stays at0.#3485: the exit-0 list in the help and README names a pull request no longer open, which this promotion's own review found missing.
#3486: where the head's rollup holds a type the reader cannot parse, the
CHECKS_MISSINGclause and theBLOCKEDhold's note say a required check may sit in that unread part, which this promotion's second review found missing.#3490: the no-check-posted cause of exit
45is scoped toDIRTYin the help and README and names its remedy, which this promotion's third review found missing.#3491: the
waithelp and README restore the caveat that a review-present exit0does not poll the checks, which this promotion's fourth review found dropped.Closes #3471
Constituent coverage
Each change this promotion carries had coverage on its own pull request into develop:
8f29cadddevelop. A Copilot round on the earlier headee530a4bopened three findings, each fixed in995ce78aand resolved, so no Copilot round covers the merged head275e4c8bdevelop, and a Copilot table on the merged head naming exactly its 2 changed files, no open findings1f9fde03develop. A Copilot round on the earlier headb8a74e8aopened three findings, each fixed later on the branch and resolved, so no Copilot round covers the merged headba37c856develop, and a Copilot table on the merged head naming exactly its 4 changed files. Its headline's test-wording preference was declined in a comment8c65e4d8develop, and a Copilot table on the merged head naming exactly its 2 changed files, verdict clean, no open findings🤖 Generated with Claude Code