Skip to content

The visual-capture retry latch can never be released once the durable poll budget ends the chain #9876

Description

@JSONbored

Three contributor PRs in JSONbored/awesome-claude (5701, 5702, 5704) have been frozen for up to an hour: the screenshot-table gate can neither close nor pass them, so nothing happens to them at all. Verified on the production ORB — visual_capture_retry_pending_sha = head_sha for all three open PRs.

Root cause

visualCaptureRetryPendingSha is a latch. It defers the gate’s one-shot CLOSE while a bounded recapture retry is still in flight for that head (#9030), so a browserless blip is not mistaken for “this PR has no visual evidence”. Only two writes release it: a successful capture, or the budget-exhausted clear added in #9462.

That clear lives inside scheduleVisualCaptureRetry, which the call site only invokes when capture.previewPending || capture.renderFailed. But the durable per-head poll budget (#6323, preview-poll-budget.ts) terminates the retry chain by suppressing previewPending once the head’s R2 budget is spent. So on the attempt that ends the chain, the condition is false, scheduleVisualCaptureRetry is never called, and the exhaustion-clear it contains never runs.

The #9462 clear is therefore unreachable in exactly the case it was written for. The comment at the gate site — “once the budget is exhausted, the marker is never set again and the gate falls through to its normal, accurate evaluation … this can never hold a PR forever” — is false as written.

Live evidence: 6 screenshot_table_close_deferred_capture_retry events per PR ~90s apart (PREVIEW_POLL_SECONDS), matching MAX_PREVIEW_POLL_ATTEMPTS = 5, then silence and a permanently-set latch.

Two further holes in the same latch

  • Mark-before-enqueue. scheduleVisualCaptureRetry writes the latch first, then enqueues the retry job with a best-effort .catch that only logs. A failed enqueue leaves a latch with no retry that could ever release it.
  • Conclusive capture does not clear. When a capture concludes with no successful bot capture, no previewPending and no renderFailed, neither branch at the call site fires, so a latch from an earlier attempt survives a run that definitively answered the question.

Fix

  1. Clear the latch at the call site whenever the capture concludes without a live retry being scheduled — the release belongs to the capture outcome, not to the scheduler that may never be called.
  2. Enqueue first, mark only after the enqueue succeeds.
  3. Give the latch a recorded timestamp and treat one older than the longest possible retry chain as expired, so the bound is arithmetic rather than a function of which code paths happened to run. This latch has now had two separate “this can never hold a PR forever” claims in comments, both false in production; a time bound is what makes the claim structural. Mirrors BUDGET_MARKER_MAX_AGE_MS on the sibling R2 marker.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions