Skip to content

fix(git): the file tree survives repositories larger than 1 MB of paths - #36

Open
gustavo-depaula wants to merge 1 commit into
nilbuild:mainfrom
gustavo-depaula:fix/tree-enobufs-large-repos
Open

fix(git): the file tree survives repositories larger than 1 MB of paths#36
gustavo-depaula wants to merge 1 commit into
nilbuild:mainfrom
gustavo-depaula:fix/tree-enobufs-large-repos

Conversation

@gustavo-depaula

@gustavo-depaula gustavo-depaula commented Aug 17, 2026

Copy link
Copy Markdown

Addresses #24 (deliberately not Closes, see the last section).

What happens

On a large repository, the file tree fails to load and the UI shows:

Failed to get tree: Error: spawnSync git ENOBUFS

Reproduced on a monorepo with ~29,000 tracked files, where git ls-files emits 2.3 MB. /api/tree and /api/tree/fingerprint both return 500, so the file browser is unusable and so is any tour, since the tour view reads the tree.

Why

getWorkingTreeFiles and getTreeFingerprint in packages/git/src/tree.ts call execFileSync without a maxBuffer, so they inherit Node's 1 MB default. Any repository whose listing exceeds that kills the child process before it can return.

packages/git/src/exec.ts already had the 50 MB ceiling these calls needed — but only on its string-command helpers (execLarge, execWithStdin), and tree.ts never used them.

The fix

tree.ts passes argv arrays rather than shell strings, and it should keep doing that: a repository path can contain a space or a quote, and building a shell string out of dirPath would break on both. So rather than converting these calls to execLarge, this adds the argv form of it:

  • execFileLarge(command, args) in exec.ts, alongside the existing helpers
  • the repeated 50 * 1024 * 1024 becomes a named MAX_BUFFER, with a comment saying what overruns the default
  • all seven execFileSync calls in tree.ts route through the new helper

Net effect is that the whole class of failure is gone from that file, not just the one call that happened to be hit first.

Testing

packages/git/tests/get-tree-large-repo.test.ts builds a temp repository whose listing crosses 1 MB and asserts getTree() returns every file. It follows the fixture style of get-diff-files.test.ts.

  • Without the fix, both cases fail with Error: spawnSync git ENOBUFS — the exact error from the report.
  • With the fix, @diffity/git is 7/7 and the full suite is 75/75 across the three packages. tsc is clean.

One note on the test: the fixture is deliberately over the limit, and git commit names every file it creates, so the setup helper needs the same headroom as the code under test. Without that it fails in beforeAll for its own reasons rather than testing anything.

What this does not fix

#24 reports two things, and this PR only fixes one of them. The reporter also notes that the failure takes down views that do not need the file tree at all:

Since the SPA surfaces that at the top level, views that don't even need the file tree (a code tour) go down with it too.

That is a separate defect — a /api/tree 500 should degrade the file browser, not replace the whole app with an error page. A tour has its own data and can render without the tree. This PR does not touch the error boundary, so the issue should stay open for that half if you agree it is worth fixing; hence no closing keyword above.

Also worth a look

packages/github/src/pr.ts uses a 10 MB buffer for gh api ... --paginate on PR comments. That is far more headroom than this was, so it is likely fine, but it is the same pattern of a per-call literal rather than the shared constant.

getWorkingTreeFiles and getTreeFingerprint called execFileSync without a
maxBuffer, so they inherited Node's 1 MB default. In a monorepo whose
git ls-files output is 2.3 MB across ~29k files, every call died with
spawnSync git ENOBUFS and the UI showed 'Failed to get tree' — the file
browser and any tour that reads the tree were unusable.

exec.ts already had the 50 MB ceiling these calls needed, but only for
its string-command helpers; tree.ts passes argv arrays, which it should,
because a repository path can contain a space or a quote. So this adds
execFileLarge as the argv form of execLarge, names the shared constant,
and routes tree.ts through it.
@gustavo-depaula
gustavo-depaula force-pushed the fix/tree-enobufs-large-repos branch from 15fb0ff to ad21099 Compare August 17, 2026 21:30
@gustavo-depaula

Copy link
Copy Markdown
Author

@nilbuild What do you think of this?

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.

1 participant