Skip to content

Git output over ~8 KiB is intermittently truncated (pinned ktsu.RunCommand 1.5.0), so Patch()/Log()/Diff() silently return partial results or malformed hunks #127

Description

@matt-edmondson

What's wrong

Directory.Packages.props:10 pins ktsu.RunCommand to 1.5.0. RunCommandGitProcessRunner (GitIntegration/Execution/RunCommandGitProcessRunner.cs:65-148) appends whatever chunks RunCommand delivers into StandardOutput and trusts it to have read the pipe to EOF.

RunCommand 1.5.0 stops reading when the process exits and then does a single final read, so anything still in the pipe after that is dropped. This was fixed upstream in ktsu-dev/RunCommand#87 (commit 0993c0e, "read output to EOF"), first released in v1.8.0. The upstream comment in AsyncProcessStreamReader.cs explains: "stopping at exit plus a final read silently dropped everything past the first few kilobytes."

Failure scenario

Reproduced with the real git runner:

  • git diff a97019c~1 a97019c in this repo is 8,769 chars. Run 200 times through GitTextBuilder, 9 of the 200 runs returned exactly 8,192 chars, cut mid-line.

  • Running Patch().Between(sha~1, sha) over the last 12 commits of every ktsu-dev repo (1,801 file patches):

    • one GitParseException: Malformed hunk header: '@@ -1' (JsonRequiredConditionally 01d3e4e)
    • seven hunks whose parsed line count disagrees with their @@ header, e.g. this repo's a97019c GitIntegration/GitRepository.cs (old 1/6, new 1/10)

    Re-running the same commits gives correct results, so the parser is fine and the input was short.

Consequences for callers:

  • When the cut lands mid-hunk, GitPatchParser (Parsing/GitPatchParser.cs:253-303) ends the hunk at the truncation point without an error, because it follows line markers and never checks OldCount/NewCount. The result is a GitHunk shorter than its header, and any later files are silently dropped.
  • Apply(file.PatchFor(...)) on such a hunk then fails as a corrupt patch.
  • A truncated Log/Status/Diff just returns fewer entries.
  • The failure is nondeterministic: about 5% of outputs over 8 KiB in these runs.

Suggested fix / acceptance criteria

  • Bump ktsu.RunCommand to ≥ 1.8.0. 1.9.0 was tested.
    • This requires moving ktsu.Semantics.Paths / ktsu.Semantics.Strings from 3.0.1 to ≥ 5.8.0, otherwise restore fails with NU1109 downgrade errors.
    • In a scratch copy with RunCommand 1.9.0 and Semantics 5.8.0, the repro showed 0/200 truncations and the full suite passed (708/708).
    • The Semantics change is a major version jump, so re-run the KTSU0006 / VersionOverride consumer-resolution check described in CLAUDE.md.
  • Add an integration test that reads output well over 8 KiB (a large Patch() or Log()) many times and asserts it is byte-identical each time.
  • Optional hardening: have GitPatchParser check each hunk's line tally against its @@ counts and throw GitParseException on mismatch, so a short read fails loudly instead of producing a malformed hunk.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreadyFully specified; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions