The wrapper gives up on a long lens at 30 minutes, and two loops and the forge requests have no limit of their own #56
Loading…
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?
The wrapper gives up on a lens that is still working after 30 minutes, although the review service allows 60. Separately, two of its loops have no end and its forge requests have no timeout, so a misbehaving service or forge keeps a run going until the workflow's
timeout-minutescancels it.The coverage window is half the service's lens limit
Stage 1 waits 30 minutes for coverage (
src/review/constants.ts:33,src/wrapper/wait.ts:98-113). The review service lets a single lens run for up to 60 minutes. A lens that takes longer than 30 minutes to finish fails the check withdid not finish coverage in time; click rerun(src/wrapper/remedies.ts:84-86), although nothing is wrong.Observed in a run of a consumer repository, on a documentation change touching 33 files (430 additions, 737 deletions). The writing-quality lens ran 35 minutes (2,119 s) and completed with exit 0. The wrapper had given up 5 minutes earlier, and the check failed with the rerun message.
Raising stage 1 to the service's limit plus slack, say 65 minutes, changes the sums. Today one wait is at most 60 minutes (30 + 25 + 5,
src/review/constants.ts:33-35). A rerun that re-triages waits a second time (src/wrapper/orchestrator.ts:104-120), so 120 minutes, which fits under the workflow'stimeout-minutes: 130(.forgejo/workflows/review.yml:77). With 65 minutes at stage 1 one wait is 95 minutes, and the same rerun could need 190 in the worst case the code allows. The other direction is to lower the service's lens limit to match. Either number can move, but they need to be set together and written down next to each other, together with the workflow's cap.Three paths have no limit of their own
src/forge/client.ts:37-48). The wrapper sets none, so the only bound is the runtime's: on Node 24 a request that gets no response headers fails after about 300 seconds. Service requests are limited to 120 s (dist/index.mjs:5749). Reproduced asd12-forge-hang: a forge that accepted the head read and never answered ended the run withfetch failed: Headers Timeout Errorafter 300.9 s, exit 1. A run makes many forge requests, since it lists every review and each review's comments twice.waitForSettlesets a new grace deadline every time triage settles, inside a loop with no cycle limit (src/wrapper/wait.ts:117,src/wrapper/wait.ts:130). The reset is deliberate (#10 made the grace run from each settle).pollreturns a result before it checks the deadline or sleeps (src/wrapper/wait.ts:28-31). A service whose triage state flips between settled and not settled on successive reads makes the loop run without sleeping and without end. Reproduced asd13-grace-flap: a stand-in service did that for 20 s without the wrapper exiting, at over 2,000 requests a second.src/review/claims-walk.ts:27-29). A service that returns a new cursor on every page is walked forever, and the walk runs inside a poll step that cannot check its deadline (src/review/claims-walk.ts:5-8). Reproduced asd14-endless-claims: a stand-in service produced more than 150,000 pages in 15 s with no end.Even where each path is bounded, nothing limits the sum. Each window is bounded on its own, and the retry ladders and request timeouts add up on top of them.
What it should do instead
AbortSignal.timeout, as the service client has.walkClaimsa page limit or a deadline.Why it matters
When the workflow's cap fires, the run is cancelled with no outcome line and a red check that does not say why. While a loop spins, it sends thousands of requests a second to the service, so the security relevance is limited to availability of the service. None of the three has been seen in production. Each needs a service that flips its state or mints cursors without end, or a forge that accepts a connection and never replies.
🤖 Generated with Claude Code