mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #263] [MERGED] feat(cursor-review): carry the 422 fallback's findings into the ledger #266
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#266
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/263
Author: @mattmillerai
Created: 9/5/2026
Status: ✅ Merged
Merged: 9/8/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-10002-fallback-sentinel📝 Commits (3)
a57f1e5feat(cursor-review): carry the 422 fallback's findings into the ledgerfb3edb8fix(cursor-review): keep a prose floor under the fallback's sentinel (BE-10002)02723c9test(cursor-review): pin the clamp-note reserve, and file the budget tests correctly📊 Changes
4 files changed (+792 additions, -77 deletions)
View changed files
📝
.github/cursor-review/build-ledger.py(+91 -29)📝
.github/cursor-review/post-review.py(+177 -23)📝
.github/cursor-review/tests/test_build_ledger.py(+133 -0)📝
.github/cursor-review/tests/test_post_review.py(+391 -25)📄 Description
ELI-5
When GitHub rejects a review's inline comments, cursor-review re-posts the whole round as plain prose. Until now that prose carried a "these findings lost their threads" sentence but not the little machine-readable copy the next round reads, so the next review knew findings had been lost and could not see a single one of them — including the findings that anchored perfectly well and only lost their thread to the failed request. This PR puts that machine-readable copy in the fallback body too, marks the findings that were lost to the failed POST (rather than to the diff), and tells the next round's panel and judge that the "prefer not to re-raise" rule for unanchorable findings does not apply to those.
The copy is a guest in that body, not its owner: it may take at most half the size budget, and if the round is too big for that it carries the most urgent findings that fit rather than all or none. The prose a human reads keeps the other half.
What changed
post-review.pyrender_body_only_sentinelemits an optional per-item"lost_to_fallback": true, only for an item that carries it and only on a literalTrue(matchingbuild-ledger.py's reader) — a success-path payload is byte-identical to before, and the key contains no-, so the existing-dash escape still makes the HTML comment unclosable.enriched: items that came from the inline half are taggedlost_to_fallback, items that were already unanchorable are not (the POST outcome told us nothing about them). They are tagged by object identity while iteratingenriched, so the sentinel keeps the same most→least-urgent order as the prose under it.load_anchors(args.diff)is kept inanchorsrather than consumed inline. When it returnsNone— no--diff, an unreadable or empty patch —partition_by_anchorfails open and calls every finding inline without testing one, so nothing is tagged and every finding renders[unanchorable]. The flag claims the finding passed the diff-anchor check, and only a real check can make that claim.review_head→ the existing_(Inline comments could not be anchored…)_note → the sentinel → the findings prose. That is the same orderrender_body_only_findingsuses and for the same two reasons —build-ledger.pyonly accepts a sentinel whose marker line sits immediately above it, andclamp_review_bodycuts the tail, so a clamp eats least-urgent prose rather than the JSON.FALLBACK_SENTINEL_MAX_CHARS = MAX_REVIEW_BODY_CHARS // 2caps what the sentinel may take, and the newfit_sentinel_items()binary-searches the longest most-urgent prefix that fits it, so a round too big for a whole sentinel recovers part of one instead of none of it. And a clamp reserve:clamp_review_body's truncation note is subtracted from the budget, not merely the limit tested, so the sentinel is never posted where the clamp would cut into it.render_findings_markdown's separator and that note are now module constants, so the guard measures the real strings.build-ledger.py_body_only_entriescopieslost_to_fallback: Trueonto the entry, matched withis True(the payload is model-adjacent text arriving through a public review body, so a stray"no"must not read as the flag). Everything mechanical is unchanged:anchoredFalse, zeroed thread, emptydiscussion_url, cap-exempt.render_ledger_markdownrenders[post-failed]instead of[unanchorable]for such an entry, with a note saying the finding matched a line in the reviewed diff and lost its thread to a delivery failure, so re-raising it needs norepeat_of.unanchorable_countnow excludes these entries and a newpost_failed_countcarries them, each with its own clause in the block header and inledger_note— so the aggregate a model reads first can no longer say "N unanchorable, so never answerable at all" of a finding whose own entry line two rows below reports that it matched the diff. Both renders read the new key with.get(...) or 0, so a ledger written before this change is unaffected._PANEL_STEERINGand_JUDGE_STEERING:[post-failed]behaves like[unanchorable]for repeat purposes (no thread, never arepeat_oftarget, costs no repeat slot) but, unlike it, passed the diff-anchor check — so the "prefer not to re-raise" preference does not apply to it.No sentinel version bump —
v1gains an optional key an older parser ignores.Two defects this PR's own review found, and fixed
The sentinel displaced the review a human reads. The first size guard only asked whether the sentinel fit ahead of finding one, which let it take the whole budget. Measured on this branch: a round of 89 findings of ~700 chars posted 58,720 characters of sentinel and rendered zero findings, while the same round at 90 findings — one over the all-or-nothing guard, so the sentinel was dropped whole — rendered 79. The cliff ran backwards, and the rounds with the most to report showed the least. With the prose floor and prefix search, every size from 60 to 130 findings now posts 39 findings visible and 45 recovered. Pinned by
test_the_sentinel_never_displaces_the_findings_a_reader_can_seeandtest_the_sentinel_keeps_the_most_urgent_findings_when_it_cannot_keep_all; reverting the cap fails 5 subtests.A ~122-character window in which the clamp cut into the JSON.
clamp_review_bodycuts atlimit - len(note), so a head+sentinel that fitsMAX_REVIEW_BODY_CHARSby less than the note's length was cut mid-payload,drop_unterminated_commentthen removed the sentinel back to its opener, and it took every finding after it. Measured on a 90-finding round: the posted review collapsed from 60,000 characters of findings to a 494-character header. The budget therefore reserves the clamp note. Because the prose floor now wins themin()on ordinary rounds, that reserve is pinned on the input where the other term wins — a panel summary large enough to make the head most of the body — and the assertion is about the prose, which is what the reserve actually saves: both settings end with no readable sentinel inside the window, but only the unreserved one also throws the findings away. Removing the reserve fails 3 of the 6 swept subtests.Tests
test_the_fallback_round_degrades_loudly_in_the_next_round_s_ledger→…carries_its_findings_into_the_next_round_s_ledger: the 422 path now posts a sentinel, and the posted body is round-tripped through the realbuild_ledger, yielding two entries — the anchorable onelost_to_fallbackand rendered[post-failed], the off-diff one plain[unanchorable].path— bodies are capped at 600 chars butpathis length-checked nowhere — so the marker is posted alone and the ledger discloses it); andFitSentinelItemsTest, a unit class for the search itself (prefix-ness, longest-prefix exactness, empty budget, empty input).test_no_finding_is_called_post_failed_when_the_anchors_were_never_checked— with no--diff, nothing is tagged and both entries stay[unanchorable].TestLostToFallbackEntries(round-trip through the real writer, the[post-failed]render and its note, steering present for both audiences, forward-compat, and that only a realtruesets the flag), plustest_the_two_thread_less_totals_are_reported_separatelyandtest_a_round_with_no_post_failed_entry_reads_exactly_as_beforefor the split aggregate.test_the_sentinel_is_never_posted_where_the_clamp_would_cut_itmeasuredlen(body), which counts the sentinel's HTML comment and so stayed large on precisely the body that rendered zero findings; it now counts rendered entries on the body with the comment line stripped, via a new module-levelvisible()helper thattest_no_finding_is_rendered_twice_in_the_fallbackalso uses.EndToEndPostTest.run_maingains an optionalpanel=argument (defaulting to today's fixture) so a test can control the one finding-independent part of the review head.test_a_422_still_degrades_wholesale_and_keeps_every_findingand the success-path sentinel tests.test_the_clamp_cuts_the_least_urgent_finding_in_the_fallbackshrinks its four finding bodies from 19,000 to 18,000 characters because the sentinel now shares the budget; the property it pins — the cut landing on the least urgent — is unchanged, and it now also asserts the cut finding is still recoverable from the sentinel.Scope swept
post-review.pyposts four kinds of review body. The two that carry findings — the success path and this wholesale fallback — now both emit a sentinel. The other two carry no findings to encode (the "no high-signal findings" review, and the error review, which deliberately defangs both halves of the contract out of imported text). The three paths that deliver only to the job summary when findings exist (read-only token, no inline half to drop, both POSTs failed) still contribute nothing to the ledger, because no review reaches the PR at all on them.Sentinel cost, for sizing: up to ~660 characters per finding (the 600-character body cap plus the path/line/severity keys). At that size the half-budget floor starts trimming the sentinel to a prefix at ~45 findings.
Residual
ghresult is not proof the review was not committed server-side. If the first POST did land, its findings have real threads and land in this sentinel, so the next ledger carries each twice: once with its thread and the author's reply, once as a cap-exempt[post-failed]entry asserting nobody could have answered it. Documented in-code where the tag is set. Confirming absence needs a read of the PR's reviews this script does not do, and a decision about what to do when that read fails — filed as a follow-up.[post-failed]entry carries no repeat lineage. The sentinel strips the renderedrepeat_ofline and carries the URL in no field, so a re-raise of a finding answered in a round that has since aged pastMAX_ROUNDScosts no repeat slot. This predates the PR — it is true of every[unanchorable]entry since BE-9565 — and the steering that amplified it ("re-raise it freely") has been softened to "re-raise it if it still holds", with a test asserting freely appears in neither audience's steering. The lineage fix is filed as a follow-up.drop_unterminated_commenttakes the demoted prose with it. Not fixed here because it changesrender_body_only_findings' signature and alters the path every review takes;fit_sentinel_itemsis now available for it. Filed as a follow-up.ghharness (post_returncode=1, stderr="HTTP 422") and the realbuild_ledgerparser, which is what the driving ticket names; no PR was 422'd against GitHub to confirm end to end.mainbefore being changed.Provenance
python3 -m unittest discover -s .github/cursor-review/tests -p 'test_*.py': 402 tests, OK (0 failures);python3 -m unittest discover -s .github/groom/tests -p 'test_*.py': 374 tests, OK;python3 -m unittest discover -s .github/agents-md-integrity/tests -p 'test_*.py': 46 tests, OK;shellcheck -x .github/cursor-review/install-cursor-cli.sh .github/cursor-review/slack-notify.shand-x .github/bump-callers/bump-callers.sh .github/bump-callers/tests/test_bump_callers.sh: clean;python3 .github/agents-md-integrity/check_agents_md.py --root .: passed (2 pre-existing warnings, both about a missing CODEOWNERS);python3 .github/workflow-pins/check_workflow_pins.py: OK, 11 workflows, 0 exempt. Mutation checks — replacingFALLBACK_SENTINEL_MAX_CHARSwith an unbounded budget fails 5 subtests across the two prose-floor tests; deleting the- len(CLAMP_TRUNCATION_NOTE)reserve fails 3 of the 6 swept reserve subtests; both go green on restore. The 58,720-vs-zero and 39-visible/45-recovered figures above were measured on this branch through the test harness, before and after the fix.clamp_review_body's truncation note rather than testingMAX_REVIEW_BODY_CHARSraw — both stricter than the plan, for the measured reasons in the defects section. Thelost_to_fallbacktag is additionally gated on a successful anchor parse, which the plan did not specify. Per this repo's public-repo guardrail the title and body carry no tracker reference — the branch name does.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.