Skip to content

fix(review): make the visual-preview poll budget durable per head SHA - #6335

Merged
loopover-orb[bot] merged 1 commit into
mainfrom
fix/visual-preview-poll-durable-budget
Jul 16, 2026
Merged

fix(review): make the visual-preview poll budget durable per head SHA#6335
loopover-orb[bot] merged 1 commit into
mainfrom
fix/visual-preview-poll-durable-budget

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • MAX_PREVIEW_POLLS (processors.ts) was meant to bound the visual-preview self-poll to 5 attempts before giving up on a preview deploy that never becomes discoverable. It did nothing in practice: the counter only lived inside the dedicated recapture-preview job chain's own attempt payload field, and at least three other re-review triggers — the CI-completion (check_run/check_suite) webhook handler, the deployment_status webhook handler, and the sweep pass — call reReviewStoredPullRequest without threading it through, so it silently reads back as 0 and re-arms a fresh 5-attempt budget every time.
  • Confirmed live on fix(ui): apply the pre-2000 placeholder rule in normalizeFreshnessSources metagraphed#6036: 7 check-runs completing over ~8 minutes, each independently capable of re-arming the countdown, produced 12+ re-review comment edits over 52+ minutes on a single PR — still ongoing when observed. Pure wasted Browser Rendering + queue/webhook cost on a PR whose "after" side could never resolve anyway (no discoverable Cloudflare Workers/Pages deployment for that repo).
  • New src/review/visual/preview-poll-budget.ts tracks attempts durably, keyed by head SHA, in an R2 marker mirroring actions-fallback.ts's own isFallbackDispatchInFlight/markFallbackDispatched pattern exactly — same fail-open-on-read-error contract, same best-effort-write contract, same max-age fail-safe expiry (24h) so a marker can never permanently block a genuinely new push.
  • buildCapture consults + increments the budget before treating a still-building preview as poll-worthy. Once exhausted for a head, previewPending becomes false and the honest "review manually" FAILED placeholder shows instead of an eternally-spinning "loading" one.
  • Because every existing caller already only reschedules when capture.previewPending is true, no other call site needed to change — the fix is fully contained to where the budget check lives, regardless of which trigger caused the buildCapture call.
  • processors.ts's local MAX_PREVIEW_POLLS is consolidated into the new module's exported MAX_PREVIEW_POLL_ATTEMPTS (one source of truth) and left in place as a now-redundant secondary bound for the dedicated self-poll job chain — harmless, but no longer the thing that actually stops a never-resolving preview from polling forever.

Closes #6323

Test plan

  • New test/unit/preview-poll-budget.test.ts (13 tests, 100% stmt/branch/line coverage): count tracking, per-head independence, max-age expiry (and that firstAttemptAt is preserved across increments, not reset), malformed/missing-field marker degradation, fail-open reads, best-effort writes
  • test/unit/visual-capture.test.ts extended: a building buildState records one attempt; past MAX_PREVIEW_POLL_ATTEMPTS for a head, buildCapture returns previewPending: false and the placeholder=failed URL, and does NOT keep incrementing past the cap
  • Full existing visual-capture.test.ts suite (106 tests) and queue-4.test.ts (89 tests, including the recapture-preview job-dispatch tests) pass unchanged — the fix is backward compatible with every existing scenario
  • npm run typecheck clean
  • Full local npm run test:ci gate green

Closes #6323

MAX_PREVIEW_POLLS (processors.ts) was meant to bound the visual-preview
self-poll to 5 attempts before giving up on a preview deploy that never
becomes discoverable. It did nothing in practice: the counter only lived
inside the dedicated recapture-preview job chain's own `attempt` payload
field, and at least three other re-review triggers (CI-completion
webhooks, deployment_status webhooks, the sweep pass) call
reReviewStoredPullRequest without threading it through -- each one
independently re-arms a fresh 5-attempt budget whenever buildCapture
reports previewPending.

Confirmed live on JSONbored/metagraphed#6036: 7 check-runs completing
over ~8 minutes, each capable of re-arming the countdown, produced 12+
re-review comment edits over 52+ minutes on a single PR, still ongoing
when observed -- pure wasted Browser Rendering + queue/webhook cost on a
PR whose "after" side could never resolve anyway.

New src/review/visual/preview-poll-budget.ts tracks attempts durably,
keyed by head SHA, in an R2 marker mirroring actions-fallback.ts's own
isFallbackDispatchInFlight/markFallbackDispatched pattern exactly
(fail-open reads, best-effort writes, a 24h max-age fail-safe expiry).
buildCapture consults + increments it before treating a still-building
preview as poll-worthy; once exhausted for a head, previewPending becomes
false and the honest "review manually" FAILED placeholder shows instead
of an eternal loading spinner. Because every caller already only
reschedules when previewPending is true, no other call site needs to
change -- the fix is fully contained to where the budget check lives.

processors.ts's local MAX_PREVIEW_POLLS is consolidated into the new
module's exported MAX_PREVIEW_POLL_ATTEMPTS (one source of truth) and
left in place as a now-redundant secondary bound for the dedicated
self-poll job chain -- harmless, but no longer the thing that actually
stops a never-resolving preview from polling forever.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 16, 2026
@loopover-orb

loopover-orb Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-16 02:43:40 UTC

5 files · 1 AI reviewer · no blockers · readiness 86/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR fixes a real bug: MAX_PREVIEW_POLLS only bounded the dedicated self-poll job chain's own payload counter, so CI-completion, deployment_status, and sweep triggers each independently re-armed a fresh budget by calling reReviewStoredPullRequest without threading the counter through, letting the poll counter reset to 0 every time. The new preview-poll-budget.ts module makes the budget durable per head SHA via an R2 marker mirroring actions-fallback.ts's existing fail-open/best-effort/max-age pattern, and buildCapture now consults + increments it before signaling previewPending, so every existing caller's 'only reschedule when previewPending' logic naturally respects the cap regardless of trigger. The change is well-scoped to the linked issue (#6323), includes solid test coverage for the new module (stale marker expiry, firstAttemptAt preservation, fail-open on read/write errors) and for buildCapture's budget-exhausted path, and the processors.ts constant rename to MAX_PREVIEW_POLL_ATTEMPTS keeps the old local check as a harmless secondary bound.

Nits — 6 non-blocking
  • preview-poll-budget.ts's readBudgetMarker + recordPreviewPollAttempt is a non-atomic read-then-write against R2, so concurrent triggers firing close together (the exact '7 check-runs in 8 minutes' scenario from the bug report) can race and momentarily undercount, letting a few extra polls through before the cap catches up — worth a one-line comment acknowledging this is an accepted approximation, not a hard guarantee.
  • The budget key is derived from headSha alone with no repo scoping (src/review/visual/preview-poll-budget.ts budgetR2Key); an identical head SHA appearing across two different repos (e.g. a shared base commit before divergent history) would share a budget marker, though this is extremely unlikely in practice given SHA is content-addressed.
  • Only the 'building' buildState path is covered by the new visual-capture.test.ts assertions; the 'succeeded' branch that also now consumes the budget isn't separately exercised.
  • Consider scoping the R2 key by repo+headSha instead of headSha alone for extra defensiveness against cross-repo SHA collisions.
  • Add a test case exercising buildState 'succeeded' going through the same budget-check path as 'building' to fully cover the changed branch in capture.ts.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #6323
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 42 registered-repo PR(s), 34 merged, 443 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 42 PR(s), 443 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: significant
Linked issue satisfaction

Partially addressed
The PR directly solves the core ask: a durable, per-head-SHA R2-backed budget in preview-poll-budget.ts is consulted and incremented inside buildCapture itself, so every trigger (self-poll, CI-completion, deployment_status, sweep) shares the same cap and a new head SHA naturally resets it, with tests exercising the exhaustion path. However, the diff shown gives no evidence of the explicitly reques

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, JavaScript, MDX, Shell, Solidity
  • Official Gittensor activity: 42 PR(s), 443 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 2 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://gittensory.aethereal.dev/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 532c715 into main Jul 16, 2026
15 checks passed
@loopover-orb
loopover-orb Bot deleted the fix/visual-preview-poll-durable-budget branch July 16, 2026 02:43
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.60%. Comparing base (7ab6071) to head (22efea6).
⚠️ Report is 36 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6335   +/-   ##
=======================================
  Coverage   95.60%   95.60%           
=======================================
  Files         596      597    +1     
  Lines       47135    47160   +25     
  Branches    15006    15014    +8     
=======================================
+ Hits        45063    45088   +25     
  Misses       1290     1290           
  Partials      782      782           
Flag Coverage Δ
shard-1 43.97% <71.42%> (-0.17%) ⬇️
shard-2 36.78% <10.71%> (+0.26%) ⬆️
shard-3 32.36% <10.71%> (-0.05%) ⬇️
shard-4 34.03% <89.28%> (-0.54%) ⬇️
shard-5 31.57% <10.71%> (-0.11%) ⬇️
shard-6 45.16% <10.71%> (+0.39%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/queue/processors.ts 95.69% <ø> (-0.01%) ⬇️
src/review/visual/capture.ts 96.11% <100.00%> (+0.11%) ⬆️
src/review/visual/preview-poll-budget.ts 100.00% <100.00%> (ø)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make the visual-preview retry budget durable per-PR-head, not per-job-chain (fixes repeated re-review storms)

1 participant