Skip to content

diff: the pane reads every file's hunks with one git process, where it started one per file - #98445

Merged
poteat merged 1 commit into
mainfrom
poteat/diff-hunks-one-child
Sep 30, 2026
Merged

poteat merged 1 commit into
mainfrom
poteat/diff-hunks-one-child

Conversation

@poteat

@poteat poteat commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Summary

TLDR: the diff pane starts one git process to read a change's hunks, where it started one per file

Up to fifty processes after every tool call become one. It matters most where starting a process is slow, as on Windows: one process can fail or run out of time where up to fifty could.

Notes

  • One git diff --raw -z -p over the drawn files' literal paths prints their names, NUL-ended and unquoted, then every patch. The n-th patch is the n-th name's; no name is read out of a line, so a space, a quote, a newline, letters outside ASCII or b/ in a name cannot move a body.
  • Each file's text goes through the same parse as before: the 400-line cut, "Large file" past 1 MB, binary, renamed, untracked and staged-then-edited rows read as they did.
  • A failed read now blanks all the hunks ("Diff unavailable" on every file) where it blanked only the failed file's; the list and totals stay. One process can fail or run out of time where up to fifty could.
  • The output limit (4 MiB) and the 5 s limit are one process's. Past the output limit, files that arrived whole keep their hunks and the rest are read by a second process; so are paths too long for one command line (12,000 characters a process).
  • Seen on git 2.43, 2.50 and 2.53, not stated by git's manual: an empty field parts names from patches, and both come in one order. An opening line that git did not quote must equal the one its name gives, or the read fails; never a body under another name.
  • A conflicted file is read like any other, and the files beside it keep their hunks. Against the index, where git prints no patch for an unmerged path, that path reads "No diff content", as before.
  • Two differences. A file replaced by a folder of its name showed the folder's files' lines in its own body; it shows its own. In a file turned link, the line naming the new side reads +++ b/<name> whatever prefix a person's git config sets.
  • Forced colour, an external diff, a textconv filter, a pager, core.quotePath and diff.renames change nothing: every pin of the old call is kept.
  • The diff also tidies what it touches: a doc comment and two lines of the register module, one test title, one fixture comment. No other behaviour changes.

Test Plan

  • claude plugin test mods/diff passes
  • Change 50 files, open /diff, watch the process list through a tool call: one git diff reads the hunks
  • Change files named with space.txt, quo"te.txt and x b/x.txt: each shows its own hunks
  • Change one file by 5 MB and two small ones after it: the first says "Large file", the others show hunks

Revert-proof: 36 new tests fail with the modules' changes reverted (the mod's own suite, run against the unchanged modules)

Changelog

@poteat
poteat enabled auto-merge (squash) September 30, 2026 19:15
@poteat
poteat merged commit 292c5b8 into main Sep 30, 2026
3 of 5 checks passed
konsta95 added a commit to konsta95/claude-code that referenced this pull request Sep 30, 2026
poteat's batched hunks read (one git child for every drawn file), fitted
into the bundle's body ownership:

- fetchBodies: each call reads its new rows with one
  pinned.fetchHunks(data, files) run through lifecycle.runBody. A read
  skipped before it starts is not a Git failure. Its answer is shown only
  while its owner and stamp are current. bodyLoads becomes anthropics#98445's set of
  asked paths.
- Lifecycle: one body read per view at a time, two across views as
  before. BODY_FETCH_CONCURRENCY, which anthropics#98445 deletes, set the old
  per-view limit.
- fetchHunks takes an optional isWanted, and a read that can no longer be
  shown starts no further child for the paths past MAX_PATHSPEC_CHARS or
  left unread by a cut answer. Without it, a split read released after
  /clear or a base switch started its second child for the ended view.
- register() doc and openOnRestore: the bundle's text and epoch guard, as
  in the anthropics#98357 merge.

Tests:
- owner-lifecycle: the three BODY_FETCH_CONCURRENCY tests count batched
  children, and the lifecycle world records every asked path. The skip
  test now queues its refresh's batch behind two held batches, so /clear
  skips it. New: a split read revoked by /clear or a base switch starts
  no further child.
- fetch-hunks: a read no longer wanted asks no further child.

Co-authored with a harness running Claude and Codex teams.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: 16cb7844-98c6-4af7-b67a-3e9d16705e7f
konsta95 added a commit to konsta95/claude-code that referenced this pull request Sep 30, 2026
main now has poteat's anthropics#98374, anthropics#98445 and anthropics#98357 as squash commits
(9778ad7, 292c5b8, 525d3b3). Its mods/ is identical to 732e167
with the three PR heads merged, which this bundle has already merged
from the heads. The six files that conflict keep the bundle's text from
the first parent, so mods/ is unchanged by this merge. CHANGELOG.md and
feed.xml come from main.

Co-authored with a harness running Claude and Codex teams.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: 16cb7844-98c6-4af7-b67a-3e9d16705e7f
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.

2 participants