Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/cursor-review/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ a fresh runner with a fresh pinned checkout there is nothing tampered left for t
minted token to meet. `tests/test_workflow_job_isolation.py` pins the property, and
[`pr-size.yml`](../workflows/pr-size.yml) uses the identical split for its comment job.

### Delivery, the body-only fallback, and a throttled POST

The review reaches the PR as one `POST /pulls/{n}/reviews`. When GitHub rejects that request over an inline position, the run retries **once** without anchors — the same findings as prose, in one body-only review — and when even that fails, or the run's token cannot write to the PR at all, the review is written to the job summary instead so the findings are never lost outright. A nonzero response is not proof nothing was written, though: GitHub answers a rate limit, a secondary rate limit or abuse detection with **429 or 403** as readily on a request it went on to *serve* as on one it refused. So before the retry the run asks the PR whether the first review actually landed. A confirmed **present** answer is reported as delivered and nothing more is written; a confirmed **absent** posts the fallback.

**Under a throttle** — a 429, one of the 403 wordings that mean "slow down", or a 403 carrying its own `Retry-After` — two more things happen. The run **waits first**, for `Retry-After` else the `X-RateLimit-Reset` window, clamped to 90 seconds, because that read goes out on the very token GitHub just throttled and an immediate one tends to come back unreadable. That wait is one shared window, so the fallback POST finishes the *remainder* rather than sitting out a second full one. And if the answer is *still* unreadable afterwards, the run declines the fallback rather than risk publishing a second copy of a review nobody can un-post: the findings go to the job summary, `posted=false` keeps the [blocking gate](#optional-make-the-review-blocking) red rather than green over an unverified write, and the step fails. When the response declares an embargo *longer* than the job can wait out — GitHub sends hour-long values on a primary rate limit — the run makes no further API call at all, since a request issued inside a declared window is what escalates the limit for the whole installation; it goes straight to the job summary and red.

For every **other** undecided failure — a 5xx, a dropped connection, a 408 or a 425 — none of which says a rate window is closed, there is no wait and no such restraint: the body-only fallback still goes out, with no finding tagged `[post-failed]`, because an unreadable list is not evidence a review landed. Every call is bounded (both POSTs, both reads, both waits) so the whole worst case stays inside the post job's ten-minute budget.

### The prior-review ledger and the repeat policy

With `ledger_prior_review` on (the default), [`build-ledger.py`](build-ledger.py) rebuilds what earlier rounds raised on this PR — each finding, its thread, and the author's replies — and splices it into the panel and judge prompts as untrusted DATA. The rule it enforces is that only an **answered** finding costs a repeat slot: a finding the author or a maintainer replied to may be raised again only if the judge emits `repeat_of` (that thread's permalink) and `repeat_round`, and at most `REPEAT_CAP` such re-raises survive per review, so a round can never be all re-litigation. A finding nobody answered is free to raise again.
Expand Down
Loading
Loading