mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #272] [MERGED] fix(cursor-review): confirm the first review is absent before tagging findings lost_to_fallback #274
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#274
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/272
Author: @mattmillerai
Created: 9/8/2026
Status: ✅ Merged
Merged: 9/8/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-12528-confirm-review-absent📝 Commits (3)
dbf920bfix(cursor-review): confirm the first review is absent before tagging findings lost_to_fallback (BE-12528)2f35f40fix(cursor-review): identify the landed review by body, not by the panel marker (BE-12528)fbcbb7ffix(cursor-review): close the round-2 panel findings on the landed-review check (BE-12528)📊 Changes
3 files changed (+779 additions, -20 deletions)
View changed files
📝
.github/cursor-review/post-review.py(+303 -14)📝
.github/cursor-review/tests/test_post_review.py(+467 -4)📝
.github/workflows/cursor-review.yml(+9 -2)📄 Description
ELI-5
When the reviewer bot posts its review, GitHub sometimes answers with an error even
though it already saved the review. Until now the script believed the error, posted a
second review, and labelled every finding "lost — the post failed". Next round then
saw each finding twice: once with its real comment thread, once with a note saying
nobody could possibly have replied to it.
Now the script checks before it decides. If GitHub rejected the request (a 4xx), the
review really is absent and nothing changes. If the error left the outcome genuinely
open (a 5xx, or the connection dropped), it reads the PR's reviews and looks: review
there → report it delivered and post nothing more; review absent → behave exactly as
before; couldn't read the list → still post the fallback, but label nothing, because a
read that failed is not the same as an answer of "no".
What changed
.github/cursor-review/post-review.pygh_post_review/is_read_only_token_error:CONSOLIDATED_MARKER— the panel discriminator, mirroringgate-unresolved.py'sconstant (and the inline
jqin the workflow'salready_revieweddup-check). Atest pins the two equal, the same way
test_build_ledger.pypins the ledger's copy.gh_http_status(result)— the statusghprints on stderr ((HTTP 422)), orNonewhen it printed none.gh_list_reviews(repo, pr)—gh api --paginate --slurp /repos/…/pulls/N/reviews.Paginated because the review being asked about is the newest one, so on a busy PR it
sits on the last page of an oldest-first list; the workflow's dup-check is the
same discriminator without pagination, which it can afford because it only has to
notice a review that already exists.
review_already_posted(...) -> True | False | None— three-valued on purpose.None(list unreadable/unparseable) must never collapse intoFalse; a guard thatcannot read its input must not answer a zero.
main()'s wholesale-fallback branch now resolveslandedbefore building anything:a 4xx short-circuits to
Falsewith no API call, everything else asks the PR.landed is True→finish_posted_review()and return.landed is None→ log it andcontinue untagged.
lost_idsgains thelanded is Falseconjunct.emit_delivery(...)+ the clamp's summary write) is extracted intoa local
finish_posted_review()closure, so the two paths that leave this body onthe PR report it through one implementation rather than two that can drift.
Residual (BE-10002)paragraph is replaced by a note describing the threeoutcomes; the
Every finding of the round is OFFERED to itcomment now says the tagis applied only when the first review is confirmed absent.
.github/workflows/cursor-review.yml— comment only. Thepost-reviewjob headernow reads "one POST, plus at most one paginated GET of the PR's reviews on a non-4xx
POST failure". No permission change:
pull-requests: writealready implies the read,and
timeout-minutes: 10is ample.Tests —
EndToEndPostTest.run_maingainsexisting_reviews/list_returncodeandalways patches
gh_list_reviews(default[[]], i.e. confirmed-absent), so no casein this suite can shell out to a real
gh. It also resets_DELIVERY_EMITTEDandcaptures
$GITHUB_OUTPUTvia a temp file, withlist_calls/outputs/notesout-parameters. Eight new cases in
FirstReviewConfirmationTestcover: 4xx tags withoutreading; 5xx-absent tags; 5xx-present posts once and reports
delivered=true,gated_findings=2, exit 0; a human author / another commit / a DISMISSED review eachfail the discriminator; an unreadable list posts untagged with
post_failed_count == 0; a status-less transport error goes through the read; themarker matches the gate's; and
gh_http_statusparsesghstderr.Acceptance criteria
delivered=true, no fallback, exit 0test_a_5xx_with_the_review_present_skips_the_fallbacktest_a_5xx_with_the_review_confirmed_absent_tags_post_failed[unanchorable],post_failed_count == 0test_an_unreadable_review_list_posts_the_fallback_untaggedThe byte-for-byte claim is measured, not asserted. I drove
origin/main'spost-review.pyand this branch's over the same fixture (one anchored + one demotedfinding, 422 stderr) with a stubbed POST, and diffed the JSON payloads: identical. The
same harness on a 502 shows old
mainposting 2 reviews where this branch posts 1.The GET was exercised against the real API, not only against my own stubs. Running
gh_list_reviews/review_already_postedread-only against this repo's public PR #263confirmed:
--slurpreturns a list-of-pages that flattens as written (12 reviews, 1page); real payloads carry
commit_id,state,user.typeandbodyexactly as thediscriminator reads them; and the function answered True for the SHA that genuinely
carries a marked Bot panel review (
a57f1e5) and False for that PR's actual headSHA, which does not. That is the one part of this change a unit test could only assert
against its own premise.
Residual
narrower. The panel (6 of 6 reviewers) was right that
body.startswith(CONSOLIDATED_MARKER)identifies "some bot panel review exists atthis SHA", not this run's: a previous round's body-only fallback and any
post_error_reviewbody both open with the marker, are Bot-authored and carry thesame
commit_id. Matching one returned True, suppressing the fallback and reportingdelivered=trueover another round's threads — trading a duplicate review for asilent loss of this round's findings. The discriminator now requires the stored
body to BE
posted_body, up to the CRLF/trailing-whitespace normalization GitHubapplies. What remains is "a prior round posted a byte-identical BODY at the same head
SHA". Round 2 correctly pushed back on my first wording of this: that is not the
same as "the same findings". The body carries the header, the counts, the severity
table and the body-only section; each inline finding's path, line and text lives in
payload["comments"], which this comparison never sees — so two rounds sharing acount and severity mix could match while differing inline. Narrower than the marker
match by a wide margin, but not nil. Closing it properly means identifying the review
by something the POST would have to return (the review id it created), which it
cannot when it errors; a body digest is the workaround, and it changes the body every
reader and the ledger parser sees, so it wants its own round.
stored a body this normalization does not reconcile, the answer is
False, which isexactly what
maindoes today (duplicate posted, findings tagged lost). A strictnessmiss costs the fix's benefit; it cannot cost more than the status quo.
gh_list_reviewsis now bounded (GH_LIST_REVIEWS_TIMEOUT_SECONDS = 60,degrading to UNKNOWN). It was left unbounded to match
gh_post_review; the panelpointed out the asymmetry that matters — this read sits AHEAD of the fallback POST and
write_step_summary, so a hang bounded only bytimeout-minutes: 10takes the roundout of both channels, where the pre-BE-12528 code posted the fallback immediately.
gh_post_reviewkeeps its own behaviour: nothing runs after it that a hang could cost.4xx only in the sense that an edge or a proxy said so, possibly about a request the
API went on to serve, so they take the read like a 5xx (
RETRYABLE_4XX_STATUSES).What still rests on a documented property of GitHub's API rather than on an
observation I could make from here is the remaining premise: that a review payload is
validated before anything is committed. It is the same premise BE-10002's
not commentsbranch already relies on, and it fails in the safe direction (a 4xxthat had written would produce today's duplicate, not something worse).
403neverreaches it:
is_read_only_token_errorintercepts that earlier.by the panel; answered rather than changed. Declining to repost on UNKNOWN would lose
the round from the PR altogether, which is worse than the duplicate it avoids, and a
retry loop cannot close a window that has no bound. The
lost_to_fallbacktag isalready withheld on that branch, so what remains possible is an extra review — never
a false claim about one.
suite, the payload diff against
origin/main, and read-only calls to the reviews API.A real 5xx from GitHub's review POST is not reproducible on demand, so the
PRESENT/UNKNOWN branches have never executed against a genuine failure — only against
faithful stubs and real API response shapes.
Provenance
python3 -m unittest discover -s .github/cursor-review/tests -p 'test_*.py': 440 passed, 0 failed;.github/agents-md-integrity/tests: 46 passed;.github/groom/tests: 374 passed, 1 skipped;shellcheck -x .github/bump-callers/bump-callers.sh .github/bump-callers/tests/test_bump_callers.sh: clean;check_agents_md.py --root .: passed (2 pre-existing warnings);cursor-review.ymlre-parsed as YAML. The 422 payload-diff harness was re-run againstorigin/mainafter the review fixes: 2 payloads each, byte-identical, zero list reads on that path. All ten fixes across both review rounds are mutation-checked — reverting any one of them turns a test red.not commentsbranch not consulting the landed-check (a gate-behaviour change, and a fix-the-fix at low severity);--jq/page-cap byte bounding (theMemoryErrorit guards is not reachable at any PR size this runs on, and--jqwould change the one payload shape verified against the real API); the body-vs-inline-comments gap in the residual (wording corrected above instead); and the throttled-403 path, which is a change to the read-only degradation guard rather than to this one and is filed as a follow-up.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.