Skip to content

Commit 6a5567e

Browse files
committed
fix(pull_request_read): reject pagination parameters the selected method does not support
1 parent 85598ba commit 6a5567e

2 files changed

Lines changed: 94 additions & 0 deletions

File tree

pkg/github/pullrequests.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ Possible options:
117117
return attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, r, ifc.LabelRepoUserContent)
118118
}
119119

120+
// Each method honours exactly one pagination style: get_review_comments
121+
// takes a GraphQL cursor (`after`), the other list methods take
122+
// `page`/`perPage`. A parameter the selected method cannot use is an
123+
// error rather than a silent no-op, so an LLM caller that passes
124+
// `after` to get_files (or `page` to get_review_comments) learns that
125+
// it did not advance the page instead of receiving the first page
126+
// again as if it had.
127+
if err := rejectUnsupportedPagination(method, args); err != nil {
128+
return utils.NewToolResultError(err.Error()), nil, nil
129+
}
130+
120131
switch method {
121132
case "get":
122133
result, err := GetPullRequest(ctx, client, deps, owner, repo, pullNumber)
@@ -2502,3 +2513,32 @@ func newGQLIntPtr(i *int32) *githubv4.Int {
25022513
gi := githubv4.Int(*i)
25032514
return &gi
25042515
}
2516+
2517+
// rejectUnsupportedPagination returns an error when the caller supplies a
2518+
// pagination parameter the selected pull_request_read method does not honour.
2519+
// get_review_comments paginates by GraphQL cursor (`after`); the other list
2520+
// methods paginate by `page`/`perPage`; `get`, `get_diff` and `get_status`
2521+
// return a single object and paginate by nothing. Without this check the
2522+
// unsupported parameter is dropped silently and the caller receives the first
2523+
// page again, indistinguishable from a successful advance.
2524+
func rejectUnsupportedPagination(method string, args map[string]any) error {
2525+
_, hasAfter := args["after"]
2526+
_, hasPage := args["page"]
2527+
_, hasPerPage := args["perPage"]
2528+
2529+
switch method {
2530+
case "get_review_comments":
2531+
if hasPage {
2532+
return fmt.Errorf("method %q paginates by cursor (perPage, after); \"page\" is not supported", method)
2533+
}
2534+
case "get_files", "get_commits", "get_reviews", "get_comments", "get_check_runs":
2535+
if hasAfter {
2536+
return fmt.Errorf("method %q paginates by page/perPage; \"after\" is not supported", method)
2537+
}
2538+
case "get", "get_diff", "get_status":
2539+
if hasAfter || hasPage || hasPerPage {
2540+
return fmt.Errorf("method %q returns a single result and does not accept pagination parameters", method)
2541+
}
2542+
}
2543+
return nil
2544+
}

pkg/github/pullrequests_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4877,3 +4877,57 @@ func TestResolveReviewThread(t *testing.T) {
48774877
})
48784878
}
48794879
}
4880+
4881+
func Test_rejectUnsupportedPagination(t *testing.T) {
4882+
tests := []struct {
4883+
name string
4884+
method string
4885+
args map[string]any
4886+
wantErr string
4887+
}{
4888+
{
4889+
name: "get_files with page is fine",
4890+
method: "get_files",
4891+
args: map[string]any{"page": float64(2), "perPage": float64(10)},
4892+
},
4893+
{
4894+
name: "get_files with after is rejected",
4895+
method: "get_files",
4896+
args: map[string]any{"after": "Y3Vyc29y"},
4897+
wantErr: `method "get_files" paginates by page/perPage; "after" is not supported`,
4898+
},
4899+
{
4900+
name: "get_review_comments with after is fine",
4901+
method: "get_review_comments",
4902+
args: map[string]any{"after": "Y3Vyc29y", "perPage": float64(10)},
4903+
},
4904+
{
4905+
name: "get_review_comments with page is rejected",
4906+
method: "get_review_comments",
4907+
args: map[string]any{"page": float64(2)},
4908+
wantErr: `method "get_review_comments" paginates by cursor (perPage, after); "page" is not supported`,
4909+
},
4910+
{
4911+
name: "get with any pagination is rejected",
4912+
method: "get",
4913+
args: map[string]any{"perPage": float64(10)},
4914+
wantErr: `method "get" returns a single result and does not accept pagination parameters`,
4915+
},
4916+
{
4917+
name: "get without pagination is fine",
4918+
method: "get",
4919+
args: map[string]any{},
4920+
},
4921+
}
4922+
4923+
for _, tc := range tests {
4924+
t.Run(tc.name, func(t *testing.T) {
4925+
err := rejectUnsupportedPagination(tc.method, tc.args)
4926+
if tc.wantErr == "" {
4927+
require.NoError(t, err)
4928+
return
4929+
}
4930+
require.EqualError(t, err, tc.wantErr)
4931+
})
4932+
}
4933+
}

0 commit comments

Comments
 (0)