Skip to content

fix(github): bound fetchLiveReviewThreadBlockers' review-thread GraphQL pagination - #7480

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
xfodev:fix/review-thread-pagination-bound-7454
Jul 20, 2026
Merged

fix(github): bound fetchLiveReviewThreadBlockers' review-thread GraphQL pagination#7480
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
xfodev:fix/review-thread-pagination-bound-7454

Conversation

@xfodev

@xfodev xfodev commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #7454. src/github/backfill.ts's fetchLiveReviewThreadBlockers — the live GraphQL read that computes unresolved review-thread merge blockers, consumed directly by the merge/close decision path — walked reviewThreads(first: 50, after: $cursor) in an unbounded for (;;) loop. It only stopped when GitHub reported no more pages, returned no nodes, or repeated a cursor, so a PR that accumulates a pathologically large number of review threads could drive an unbounded number of sequential GraphQL calls every time merge-readiness was evaluated — unlike every other multi-page loop in src/github/** (PR_DETAIL_MAX_PAGES, MAX_WORKFLOW_RUN_LIST_PAGES, REVIEW_PAGE_LIMIT, COMMENT_SEARCH_PAGE_LIMIT, MAX_REPO_PAGES), all of which carry an explicit page cap.

Fix

  • Added REVIEW_THREAD_MAX_PAGES = 10 (matching the file's own PR_DETAIL_MAX_PAGES and app.ts's MAX_WORKFLOW_RUN_LIST_PAGES) and converted the for (;;) loop to for (let page = 0; page < REVIEW_THREAD_MAX_PAGES; page += 1). All existing early-stop conditions (no next page / no nodes / repeated cursor) are preserved; hitting the cap simply falls through to processing the threads gathered so far — fail-open, matching this function's documented "GraphQL unavailable → []" posture — and never throws. 10 × 50 = 500 threads is far beyond any real PR.
  • Documented (rather than paginated) the inner comments(first: 20) connection, the issue's sanctioned lower-cost option: a thread blocks merge only while it's unresolved/non-outdated (a thread-level flag, independent of comment count), and the authorizing comment is the thread-opening review comment (index 0) or an early reply, so 20 covers it with wide margin. Missing an authorizer buried past feat(signals): implement issue quality reports for opportunity ranking #20 only fails open (no blocker), so full nested pagination — N threads × M comment-pages of extra round-trips — isn't worth it for a rare edge with fail-open semantics.

Tests

Extended test/unit/backfill-2.test.ts's fetchLiveReviewThreadBlockers describe block (per the issue) with mocked multi-page GraphQL responses: a 3-page walk that collects a blocker on page 3 and terminates on hasNextPage: false (asserts exactly 3 GraphQL calls, well under the cap), and a pathological always-hasNextPage: true (distinct cursors so the seen-cursor guard never trips first) that stops at exactly REVIEW_THREAD_MAX_PAGES (10) calls and fails open to [] without throwing. Every changed line + branch is covered (verified via --coverage), so codecov/patch holds; all 134 existing backfill-2 tests pass unchanged.

Validation

  • git diff --check
  • npm run typecheck (root tsc --noEmit) green
  • npm run test:coverage on the affected suite: 100% of changed lines + branches covered; all existing backfill-2 tests pass unmodified
  • Scope is src/github/backfill.ts + its unit test only — an internal GitHub helper, no API/OpenAPI/MCP/UI/DB/wrangler surface, so no generated artifact needs regeneration

Safety

  • No secrets, wallets, hotkeys/coldkeys, PATs, private keys, raw trust scores, private rankings, or maintainer evidence.
  • The maintainer/collaborator/scanner-bot authorization gate on which comments can create blockers is unchanged — bounding pagination doesn't widen who can influence merge/close state.
  • Auth/CORS/session: N/A. API/OpenAPI/MCP: N/A. UI: N/A. No changelog edit; no site//CNAME/lovable changes.

Closes #7454

…QL pagination

The reviewThreads(first: 50, after: cursor) loop in fetchLiveReviewThreadBlockers
was an unbounded for(;;) that only stopped on no-next-page / no-nodes / a repeated
cursor, so a PR with a pathologically large number of review threads could drive an
unbounded number of sequential GraphQL calls on every merge-readiness evaluation --
unlike every other paginated list-fetch in src/github/**. Add REVIEW_THREAD_MAX_PAGES=10
(mirroring PR_DETAIL_MAX_PAGES / MAX_WORKFLOW_RUN_LIST_PAGES) and bound the loop;
reaching the cap returns the blockers gathered so far (fail-open), never throws. Also
documents why the inner comments(first: 20) connection is intentionally not paginated
(thread-level resolved/outdated flags gate blocking, and a missed authorizer past JSONbored#20
only fails open). Adds regression tests for the multi-page walk and the page-cap bound.

Closes JSONbored#7454
@xfodev
xfodev requested a review from JSONbored as a code owner July 20, 2026 11:57
@superagent-security

Copy link
Copy Markdown
Contributor

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

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.35%. Comparing base (dca5d66) to head (2a0dcb5).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7480      +/-   ##
==========================================
- Coverage   91.36%   91.35%   -0.02%     
==========================================
  Files         717      717              
  Lines       73022    73023       +1     
  Branches    21629    21631       +2     
==========================================
- Hits        66719    66709      -10     
- Misses       5265     5272       +7     
- Partials     1038     1042       +4     
Flag Coverage Δ
shard-1 34.08% <100.00%> (-0.09%) ⬇️
shard-2 39.27% <100.00%> (-0.04%) ⬇️
shard-3 32.61% <100.00%> (+0.01%) ⬆️
shard-4 39.53% <100.00%> (+<0.01%) ⬆️
shard-5 36.57% <100.00%> (-0.02%) ⬇️
shard-6 34.32% <100.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
src/github/backfill.ts 95.92% <100.00%> (+<0.01%) ⬆️

... and 2 files with indirect coverage changes

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

loopover-orb Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-20 12:10:46 UTC

2 files · 1 AI reviewer · no blockers · readiness 95/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This adds a 10-page cap (REVIEW_THREAD_MAX_PAGES = 10, matching PR_DETAIL_MAX_PAGES/MAX_WORKFLOW_RUN_LIST_PAGES conventions elsewhere in this file) to the previously unbounded reviewThreads GraphQL pagination loop in fetchLiveReviewThreadBlockers, preserving all existing early-stop conditions and falling through fail-open on cap-hit rather than throwing. The change is small, correctly scoped, and matches the described defect exactly — the loop change from `for (;;)` to a bounded `for` is straightforward and low-risk. Tests cover both the normal multi-page termination path and the pathological always-hasNextPage case, verifying the cap is actually enforced (10 calls) and that it fails open with no blockers.

Nits — 4 non-blocking
  • The inner `comments(first: 20)` connection is documented but still unpaginated — a comment authorizer buried past index 20 silently fails open with no blocker, which is intentional per the PR description but worth a one-line runtime log/metric if this ever becomes observable in practice.
  • The GraphQL query comment additions (backfill.ts) embed multi-line prose inside a template literal comment block; consider trimming to keep the query string comments shorter for readability.
  • Consider extracting REVIEW_THREAD_MAX_PAGES alongside PR_DETAIL_MAX_PAGES near the top of the file if there's a shared constants block, for discoverability.
  • If a future issue needs full comment-thread authorization beyond 20 comments, the PR's rationale (thread-level resolved/outdated flag, early-comment authorization) should be revisited then rather than pre-emptively paginated now.

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 #7454
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low 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: 55 registered-repo PR(s), 30 merged, 0 issue(s).
Contributor context ✅ Confirmed Gittensor contributor xfodev; Gittensor profile; 55 PR(s), 0 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: minor
Linked issue satisfaction

Addressed
The PR adds the named REVIEW_THREAD_MAX_PAGES=10 constant and bounds the outer for(;;) loop with fail-open fallthrough, documents (rather than paginates) the inner comments(first:20) connection as explicitly sanctioned by the issue, and adds two regression tests in backfill-2.test.ts covering multi-page collection and cap termination. All three deliverables (bounded outer loop, justified/documente

Review context
  • Author: xfodev
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: JavaScript, TypeScript
  • Official Gittensor activity: 55 PR(s), 0 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Triage stale or unlinked PRs.
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 &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; 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 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 dfcc2f9 into JSONbored:main Jul 20, 2026
15 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.

fix(github): bound fetchLiveReviewThreadBlockers' unbounded GraphQL pagination loop

1 participant