Skip to content

repo_get_pull_request_changes: tail additions silently dropped when preceding context exceeds line limit #1340

@glyphstalker

Description

@glyphstalker

Note: This is a bug in the local npm package (@azure-devops/mcp), not the Azure DevOps Remote MCP Server.

Summary

When a file has lines added at the end and the preceding unchanged content exceeds getFileDiffs's context-line limit, the context block is truncated and the add block that should follow it is silently dropped. The file appears unchanged in the MCP output even though it contains substantive additions.

Version

@azure-devops/mcp v2.7.0

Reproduction

  1. Have a PR where a file has new lines added at the very end (e.g., a new assembly attribute appended to AssemblyInfo.cs), with a large block of unchanged lines preceding the addition.
  2. Call repo_get_pull_request_changes with includeDiffs: true, includeLineContent: true.
  3. Inspect the returned lineDiffBlocks for that file -- only changeType: 0 (context) blocks are returned, ending before the actual addition. The add block is absent with no indication of truncation.

Concrete example: A 37-line AssemblyInfo.cs in the base commit, with [assembly: InternalsVisibleTo("MyProject.Tests")] added as line 38 in the PR. The MCP tool returns a single context block covering lines 1-36 and nothing more. The addition at line 38 is completely invisible -- the file appears unchanged.

Root Cause

In src/tools/repositories.ts, getFiileDiffs is called with no context-line count parameter:

const batchDiffs = await gitApi.getFileDiffs(
  {
    baseVersionCommit: baseCommitId,
    targetVersionCommit: targetCommitId,
    fileDiffParams: batch,
  },
  project || "",
  repositoryId
);

ADO's API applies a default context-line limit. When that limit is reached before the end of the file, the remaining context lines and any add/delete blocks that follow are silently dropped. The wrapper has no mechanism to detect that a file's diff is incomplete.

Suggested Fix

The wrapper already fetches baseLines and targetLines for line-content enrichment. After enrichment, it could check whether the last block's coverage falls short of either file:

Current - One expression:

const enrichedDiff = {
  ...entry.diff,
  lineDiffBlocks: entry.diff.lineDiffBlocks?.map((block: any) => { ... }),
};

Proposed - Split to allow for inspection before returning:

const enrichedBlocks = entry.diff.lineDiffBlocks?.map((block: any) => { ... }) ?? [];

const lastBlock = enrichedBlocks[enrichedBlocks.length - 1];
if (lastBlock) {
  const lastOriginalLine = (lastBlock.originalLineNumberStart ?? 0) + (lastBlock.originalLinesCount ?? 0);
  const lastModifiedLine = (lastBlock.modifiedLineNumberStart ?? 0) + (lastBlock.modifiedLinesCount ?? 0);
  if (lastOriginalLine < baseLines.length || lastModifiedLine < targetLines.length) {
    // synthesize trailing block for the missing lines
  }
};

const enrichedDiff = { ...entry.diff, lineDiffBlocks: entrichedBlocks };

This requires no ADO API changes -- all the data needed is already in memory.

Related

Issue #1237 / PR #1259 is a related but distinct failure mode (entirely new or deleted files getting no content at all). This issue affects modified files where an add block at the tail is dropped due to the context-line limit on the preceding unchanged block.

Metadata

Metadata

Labels

Needs Review 👓needs review by the product teamRepos 📁issue in the repos area

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions