Skip to content

Promote the Held-Wait Exit 45 for Missing Required Checks to Main - #3484

Merged
ptr727 merged 5 commits into
mainfrom
develop
Oct 26, 2026
Merged

ptr727 merged 5 commits into
mainfrom
develop

Conversation

@ptr727

@ptr727 ptr727 commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

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.

Closes #3471

Constituent coverage

Each change this promotion carries had coverage on its own pull request into develop:

Pull request Merged head Coverage
#3483 8f29cadd 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
#3485 275e4c8b attested local pass at the merged head, base develop, and a Copilot table on the merged head naming exactly its 2 changed files, no open findings
#3486 1f9fde03 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
#3490 ba37c856 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
#3491 8c65e4d8 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

🤖 Generated with Claude Code

…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>
Copilot AI lite review requested due to automatic review settings October 25, 2026 22:05
@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ 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.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3484      +/-   ##
==========================================
+ Coverage   81.00%   81.04%   +0.04%     
==========================================
  Files          25       25              
  Lines        8903     8922      +19     
==========================================
+ Hits         7212     7231      +19     
  Misses       1691     1691              
Flag Coverage Δ
python-3.13 81.04% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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.

2 open issues
What changed in this PR

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.

Review effort: Lite

Comment thread scripts/README.md Outdated
Comment thread scripts/pr_review.py Outdated
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium severity 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.

2 resolved since last review

Review effort: Lite

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

ptr727 commented Oct 25, 2026

Copy link
Copy Markdown
Owner Author

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low severity 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.

Low severity 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.

Low severity 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.

Review effort: Lite

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

ptr727 commented Oct 25, 2026

Copy link
Copy Markdown
Owner Author

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low severity 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.

Review effort: Lite

…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>
@ptr727

ptr727 commented Oct 25, 2026

Copy link
Copy Markdown
Owner Author

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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.

No open issues 🎉

Review effort: Lite

@ptr727

ptr727 commented Oct 26, 2026

Copy link
Copy Markdown
Owner Author

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.

@ptr727
ptr727 merged commit 1b5b677 into main Oct 26, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A Held Wait Exits 0 Under a DIRTY or BEHIND Merge Whose Required Checks Never Ran

2 participants