Skip to content

repo_pull_request get: includeChangedFiles silently stops at 100 files #1611

Closed

Description

@saryn17

repo_pull_request with action: "get" and includeChangedFiles: true says it returns "the list of changed files". For any PR whose latest iteration has more than 100 changes, it returns the first 100 and reports

fileCount: 100

with nothing that says "partial". The consumer of that output is a model, and a model reads fileCount: 100 as the total. Ask an agent to review a 300-file PR through this tool and it reviews 100 files and says it's done.

Since #1443 removed get_pull_request_changes, this is the only way to get a PR's file list out of the server, so there's no other tool to fall back to.

Where it comes from

The get branch fetches the latest iteration and then makes one call with no top / skip:

const changes = await gitApi.getPullRequestIterationChanges(repositoryId, pullRequestId, latestIteration.id, project);

src/tools/repositories.ts#L288 on main (e24cc98). The file hasn't changed since v2.10.0, which is latest on npm, so the same line 288 is in the release.

That endpoint pages. The REST docs for $top: "The default value is 100 and the maximum value is 2000." The response carries nextSkip / nextTop, documented as "Value to specify as skip to get the next page of changes. This will be zero if there are no more changes."

The SDK does not follow those for you. package-lock.json pins azure-devops-node-api@15.1.2; I pulled that tarball from npm, the sha512 matches the lockfile, and GitApi.js in it builds $top / $skip / $compareTo from the arguments, does a single rest.get, and returns the result. No loop.

fileCount is just changes.changeEntries.length, i.e. the page size. nextSkip / nextTop are copied into the response, so the information that there is more is technically present, but the tool gives you nothing to do with it: top and skip are on the schema, but they're documented "Used for list", and the get branch never passes them. If you notice nextSkip: 100 you're stuck.

Reproducing it

Take any PR whose latest iteration has 101+ changed files and call

{ "action": "get", "repositoryId": "<repo>", "pullRequestId": <id>, "includeChangedFiles": true }

I did this against a real org, on a PR whose latest iteration has 150 changed files (a throwaway repo, since deleted), with the published @azure-devops/mcp@2.10.0:

fileCount            = 100
changeEntries.length = 100
nextSkip / nextTop   = 100 / 50

The endpoint directly, same iteration: no query string → 100 entries with nextSkip: 100; $skip=100 → the remaining 50; $top=2000 → all 150 in one call. One thing the REST docs don't say: on the last page the response simply omits nextSkip / nextTop rather than sending 0, so a loop has to treat "absent" as 0.

With the loop below applied locally, the same call returns fileCount: 150 and 150 entries.

Nothing in the test suite catches this. The includeChangedFiles test from #1117 mocks two entries, and no test in the repo mocks nextSkip at all.

What I'd change

Two things, both inside the get branch:

  1. Ask for the endpoint's maximum page ($top=2000) instead of the default 100, and pass the schema's skip through — it is already on repo_pull_request, wired for list only. That makes a single call complete for anything up to 2000 files, and gives the caller a way to continue when it isn't: when nextSkip comes back non-zero, return it with hasMore: true, and say in the parameter description that fileCount is what this page holds and skip fetches the next one. That is how every other paged tool in this server works (continuationToken on pipelines, test plans, wiki, core), and it keeps the tool a thin layer over the REST call.

  2. Trim each entry to changeTrackingId, changeType, path (plus originalPath for renames). The API attaches objectId, commitId, url and more to every item, which makes a raw entry about 600 bytes and a full 2000-entry page ~1.2 MB. Add lightweight tool to list changed files in a pull request #958 described this feature as "file paths and change types (add, edit, delete, rename) — no diffs, no file content, just metadata", and that is what a model needs from it.

I first tried looping inside the tool until nextSkip ran out; the payload numbers above are why I dropped that — handing a model several MB in one response is not better than handing it 100 files, and a cursor you cannot pass back in is a dead end either way.

One caveat with caller-driven paging: the get branch picks iterations[iterations.length - 1] fresh on every call, so page 2 could land on a newer iteration than page 1 if someone pushes in between. secondComparingIteration is in every summary, so a caller can notice; pinning it properly is what #1551 is about and doesn't need to block this.

I can send a PR for the above, with tests for the page / skip / hasMore contract and the trimming, if that's the direction you want.

Related

Refs: repositories.ts@e24cc989 · package-lock.json · SDK 15.1.2 GitApi.ts · REST: Pull Request Iteration Changes - Get

Activity

  1. added 2 commits that reference this issue on Sep 17, 2026
    b21c1aa
    e51665a
  2. danhellem commented on Sep 17, 2026

    @danhellem
    Collaborator

    I don't think this is something we are going to change in the short term. You are welcome to create a PR so we can see the scope of changes. But we are very reluctant to make any big changes to the tools at this point. If your changes are small and concise, we have no problem considering them for merge. If you have a lot of changes, we will like not to consider :(

  3. saryn17 commented on Sep 17, 2026

    @saryn17
    ContributorAuthor

    PR is up: #1615. Kept to the paging fix only — trimming is a separate follow-up if you want it.

  4. added and removed
    Waiting for Author 🔨waiting for author to address feedback or create a pull request
    on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions