Skip to content

fix(ui_get): bound pagination to cap latency#2766

Merged
mattdholloway merged 2 commits into
mainfrom
mattdholloway-ui-get-pagination-guard
Jun 25, 2026
Merged

fix(ui_get): bound pagination to cap latency#2766
mattdholloway merged 2 commits into
mainfrom
mattdholloway-ui-get-pagination-guard

Conversation

@mattdholloway

@mattdholloway mattdholloway commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Problem

ui_get backs a synchronous UI picker — the label/assignee/milestone/branch/reviewer dropdowns in the MCP App issue/PR write surfaces. Each handler paginated GitHub API results in an unbounded loop (PerPage: 100, looping until NextPage == 0 / HasNextPage == false).

On very large repos/orgs this fans out into dozens of sequential API round-trips, and a single slow/stalled page inflates the whole call. Production telemetry showed ui_get p50 at sub-second but the tail spiking to ~20 minutes (max/p99 ~1.17M–1.29M ms).

Fix

Bound every ui_get pagination loop to a shared package-level constant uiGetMaxPages = 10 (≈1000 items at PerPage 100) and add an additive has_more boolean to each response indicating results were deliberately truncated. The tool is now fast-or-bounded instead of unbounded.

Truncation is an accepted product behavior here: the picker pairs it with typeahead, so responsiveness matters more than completeness.

has_more is computed as "there were more pages we deliberately did not fetch":

  • REST loops: page cap hit and resp.NextPage != 0.
  • GraphQL labels loop: page cap hit and PageInfo.HasNextPage still true.
  • uiGetReviewers: true if either the collaborators loop or the teams loop was truncated.

Affected methods

Method Pagination Notes
uiGetLabels GraphQL cursor loop totalCount stays the server-reported full repo count, so len(labels) < totalCount when truncated — expected
uiGetAssignees REST totalCount = len(result) (bounded count)
uiGetMilestones REST totalCount = len(result)
uiGetBranches REST totalCount = len(result)
uiGetReviewers two REST loops (collaborators + teams) shared has_more

uiGetIssueTypes and issue_fields are single-request (no loop) and intentionally left unchanged.

ui_get backs a synchronous UI picker (label/assignee/etc. dropdowns in the
MCP App issue/PR write surfaces). Each handler paginated GitHub API results
in an unbounded loop (PerPage 100, looping until NextPage==0 /
HasNextPage==false). On very large repos/orgs this fans out into dozens of
sequential round-trips, and a single slow page inflates the whole call —
production telemetry showed a tail spiking to ~20 minutes.

Bound every ui_get pagination loop to uiGetMaxPages (10 pages, ~1000 items)
and add an additive "has_more" flag indicating results were truncated.
Truncation is acceptable here because the picker pairs it with typeahead, so
responsiveness matters more than completeness.

Affected methods: labels, assignees, milestones, branches, reviewers
(both the collaborators and teams loops). uiGetIssueTypes and issue_fields
are single-request and left unchanged.

has_more is response-only and backward-compatible: no existing keys are
removed or renamed, and the tool input schema is unchanged. HTTP-client
timeouts are intentionally a separate follow-up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 25, 2026 09:53
@mattdholloway mattdholloway requested a review from a team as a code owner June 25, 2026 09:53

Copilot AI 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.

Pull request overview

This PR bounds pagination in the ui_get tool’s synchronous picker endpoints to cap tail latency on large repos/orgs, and surfaces intentional truncation to the UI via an additive has_more response field.

Changes:

  • Introduces a shared uiGetMaxPages cap and applies it to all paginated ui_get handlers (REST and GraphQL).
  • Adds has_more to paginated handler responses to indicate deliberate truncation when the page cap is hit.
  • Extends pkg/github/ui_tools_test.go with within-cap and truncation test cases (including a custom transport for multi-page GraphQL label queries).
Show a summary per file
File Description
pkg/github/ui_tools.go Caps ui_get pagination loops and emits has_more when results are truncated due to the cap.
pkg/github/ui_tools_test.go Adds/extends tests to validate the page cap behavior and has_more semantics for bounded and truncated cases.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 0

@mattdholloway mattdholloway merged commit 63d313a into main Jun 25, 2026
19 checks passed
@mattdholloway mattdholloway deleted the mattdholloway-ui-get-pagination-guard branch June 25, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants