Skip to content

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

Description

@JSONbored

Context

src/github/backfill.ts's fetchLiveReviewThreadBlockers (~line 3841) is the live GraphQL read that
computes unresolved review-thread merge blockers — consumed directly by the merge/close decision path
(src/queue/processors.ts, src/services/agent-approval-queue.ts,
src/services/agent-action-executor.ts).

Its pagination loop (for (;;) { ... }, ~3854-3899) walks reviewThreads(first: 50, after: $cursor)
using GraphQL cursor pagination, and correctly requests pageInfo { hasNextPage endCursor }. However,
unlike every other multi-page pagination loop in src/github/**PR_DETAIL_MAX_PAGES in this same
file, MAX_WORKFLOW_RUN_LIST_PAGES in app.ts, REVIEW_PAGE_LIMIT/ISSUE_EVENTS_RECENT_PAGE_LIMIT
in pr-actions.ts, COMMENT_SEARCH_PAGE_LIMIT in comments.ts, MAX_REPO_PAGES in public.ts
this loop has no maximum-page bound. It only stops when GitHub reports no more pages, returns no
nodes, or (defensively) repeats a cursor it has already seen. A PR that accumulates a very large
number of review threads can drive this function through an unbounded number of sequential GraphQL
calls every time merge-readiness is evaluated for that PR.

The same function's inner comments(first: 20) connection (per review thread) also has no pageInfo
and is not paginated, so a thread with more than 20 comments only sees the first 20.

Requirements

  • Add an explicit page-cap constant for fetchLiveReviewThreadBlockers's outer reviewThreads loop,
    following this codebase's existing naming convention (e.g. REVIEW_THREAD_MAX_PAGES), and stop the
    loop once that cap is reached — mirroring MAX_WORKFLOW_RUN_LIST_PAGES's exact rationale ("bounded
    so a pathological repo/PR can't turn one call into an unbounded fetch loop").
  • When the cap is hit, return whatever blockers were derived from the threads collected so far (same
    fail-open philosophy already documented at the top of this function — "if GraphQL is unavailable
    this fails open to [] rather than guessing") rather than throwing.
  • Paginate the inner comments(first: 20) connection per thread the same way, OR (lower-cost
    alternative, acceptable if scoping the full nested pagination is judged disproportionate to the
    risk) explicitly document in a code comment why 20 is treated as sufficient for blocker detection —
    do not leave it silently unbounded-but-uncommented as it is today.
  • Add a regression test that simulates at least 3 pages of reviewThreads (via mocked GraphQL
    responses) and confirms the loop terminates at the new cap without exhausting it in the common case,
    plus a test that a >20-comment thread's blocker classification is not broken by the change made (if
    the inner pagination is implemented) or remains covered by an explicit comment (if not).

Deliverables

  • fetchLiveReviewThreadBlockers's outer pagination loop is bounded by an explicit, named page-cap
    constant.
  • The inner comments(first: 20) connection is either paginated or has an explicit code comment
    justifying the fixed cap.
  • A regression test exercising hasNextPage: true across multiple simulated pages, proving both
    correct collection across pages and correct termination at the cap.

Test Coverage Requirements

This repo's Codecov patch gate is 99%+ branch-counted on every changed line/branch in src/**. The
new page-cap branch and the multi-page collection path both need explicit coverage — extend the
existing fetchLiveReviewThreadBlockers describe block in test/unit/backfill-2.test.ts (currently
single-page fixtures only) rather than adding a new file.

Expected Outcome

fetchLiveReviewThreadBlockers can no longer be driven into an unbounded number of GitHub GraphQL
calls by a PR with a pathologically large number of review threads, matching the bounded-pagination
convention every other list-fetch in src/github/** already follows — and the loop's multi-page
branch is actually proven correct by a test, not just present in the code.

Links & Resources

  • src/github/backfill.ts: fetchLiveReviewThreadBlockers (~3841), its unbounded for (;;) loop
    (~3854-3899).
  • Precedent constants: MAX_WORKFLOW_RUN_LIST_PAGES (src/github/app.ts), PR_DETAIL_MAX_PAGES
    (src/github/backfill.ts), REVIEW_PAGE_LIMIT/ISSUE_EVENTS_RECENT_PAGE_LIMIT
    (src/github/pr-actions.ts), COMMENT_SEARCH_PAGE_LIMIT (src/github/comments.ts),
    MAX_REPO_PAGES (src/github/public.ts).
  • test/unit/backfill-2.test.ts's fetchLiveReviewThreadBlockers describe block (~line 1419).

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions