Skip to content

fix(review): bound the render-failure capture-retry chain with a durable per-head budget - #10151

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
bitfathers94:fix/issue-10061
Jul 31, 2026
Merged

fix(review): bound the render-failure capture-retry chain with a durable per-head budget#10151
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
bitfathers94:fix/issue-10061

Conversation

@bitfathers94

Copy link
Copy Markdown
Contributor

fix(review): bound the render-failure capture-retry chain with a durable per-head budget

scheduleVisualCaptureRetry was bounding its retry chain on args.previewPollAttempt, the
recapture-preview job chain's own payload field. Every other trigger that reaches it
(CI-completion, deployment_status, the maintenance sweep) calls it without threading that
field, so it always read back 0 and never bounded anything for them -- a sustained
renderer outage produced an unbounded number of retries and kept re-stamping the
visual-capture retry latch, defeating its age bound.

Add a second durable, headSha-keyed budget in preview-poll-budget.ts under its own R2
namespace (mirroring the existing preview-poll budget's marker shape, key derivation, and
CAS write loop) so a previewPending retry never double-charges it and vice versa.
scheduleVisualCaptureRetry now consults that budget instead of the payload field and
increments it on every retry it actually enqueues.

Closes #10061

…ble per-head budget

scheduleVisualCaptureRetry was bounding its retry chain on args.previewPollAttempt, the
recapture-preview job chain's own payload field. Every other trigger that reaches it
(CI-completion, deployment_status, the maintenance sweep) calls it without threading that
field, so it always read back 0 and never bounded anything for them -- a sustained
renderer outage produced an unbounded number of retries and kept re-stamping the
visual-capture retry latch, defeating its age bound.

Add a second durable, headSha-keyed budget in preview-poll-budget.ts under its own R2
namespace (mirroring the existing preview-poll budget's marker shape, key derivation, and
CAS write loop) so a previewPending retry never double-charges it and vice versa.
scheduleVisualCaptureRetry now consults that budget instead of the payload field and
increments it on every retry it actually enqueues.
@bitfathers94
bitfathers94 requested a review from JSONbored as a code owner July 31, 2026 09:29
@superagent-security

Copy link
Copy Markdown
Contributor

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

@loopover-orb

loopover-orb Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-31 09:58:00 UTC

4 files · 1 AI reviewer · no blockers · readiness 73/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR fixes a real bug: scheduleVisualCaptureRetry previously bounded its retry chain on args.previewPollAttempt, a field that only the recapture-preview job chain threads through, so CI-completion/deployment_status/sweep triggers always read 0 and never bounded their retries. The fix adds a second durable, headSha-keyed budget (captureRetryAttemptCount/recordCaptureRetryAttempt) in preview-poll-budget.ts under its own R2 namespace, consulted and incremented directly in scheduleVisualCaptureRetry (src/queue/processors.ts:9977-10029) instead of the payload field, and includes tests that explicitly drive multiple independent trigger deliveries to prove the total retry count is now capped and the latch is cleared once exhausted. The refactor of readBudgetMarker/recordBudgetAttempt into shared helpers preserves the existing CAS/fail-open/best-effort contracts for both budgets, and the new tests appropriately isolate the two namespaces from each other.

Nits — 5 non-blocking
  • src/review/visual/preview-poll-budget.ts:64 hardcodes the fingerprint slice length 40 with no named constant explaining the choice — worth a short comment or const if it's load-bearing for R2 key collision avoidance.
  • The v8-ignore comment on `args.pr.headSha ?` in scheduleVisualCaptureRetry (src/queue/processors.ts) asserts the falsy arm is unreachable by construction; worth double-checking that assumption holds for the maintenance-sweep trigger path too, not just the recapture-preview job chain.
  • Consider a brief note in the module doc comment on why MAX_CAPTURE_RETRY_ATTEMPTS intentionally shares MAX_PREVIEW_POLL_ATTEMPTS rather than being independently tunable, since a future maintainer might otherwise 'fix' this by splitting them.
  • The new keySuffix/r2Namespace parameterization of budgetR2Key/readBudgetMarker/recordBudgetAttempt is a clean, minimal generalization — no changes needed there.
  • Readiness score is below the configured threshold — Use the readiness panel as advisory maintainer context; the score does not block this PR.

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 #10061
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 ⚠️ 12/25 Preflight needs author follow-up before maintainer review.
Contributor workload ✅ 10/10 Author activity: 119 registered-repo PR(s), 82 merged, 3 issue(s).
Contributor context ✅ Confirmed Gittensor contributor bitfathers94; Gittensor profile; 119 PR(s), 3 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: significant
Linked issue satisfaction

Addressed
The PR adds a durable, headSha-keyed capture-retry budget in a separate R2 namespace, switches scheduleVisualCaptureRetry's exhaustion check from args.previewPollAttempt to this new counter (incrementing it only on actual enqueues), preserves the previewPollAttempt payload threading, the enqueue-then-mark ordering, and the exhausted-budget clear behavior, and adds tests covering independence from

Review context
  • Author: bitfathers94
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 119 PR(s), 3 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Add a concise scope and risk note.
  • 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://loopover.ai/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 added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 31, 2026
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.94%. Comparing base (1807ff4) to head (912c736).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #10151      +/-   ##
==========================================
+ Coverage   80.13%   80.94%   +0.81%     
==========================================
  Files         282      284       +2     
  Lines       58742    62246    +3504     
  Branches     6963     8250    +1287     
==========================================
+ Hits        47070    50387    +3317     
- Misses      11381    11447      +66     
- Partials      291      412     +121     
Flag Coverage Δ
backend 94.66% <100.00%> (?)

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

Files with missing lines Coverage Δ
src/queue/processors.ts 94.61% <100.00%> (ø)
src/review/visual/preview-poll-budget.ts 100.00% <100.00%> (ø)

@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 4736302 into JSONbored:main Jul 31, 2026
8 checks passed
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.

visual(capture): bound the render-failure retry chain with a durable per-head budget

1 participant