Skip to content

runCommand sets maxBuffer: options.maxBuffer — the ENOBUFS fix from #179 works only because a spread undefined deletes Node's default #744

Description

@thecury

Summary

runCommand in plugins/codex/scripts/lib/process.mjs:10 sets maxBuffer: options.maxBuffer. No caller except measureGitOutputBytes (lib/git.mjs:41) ever supplies a value, so for every other command that property is literally undefined.

That is not the same as omitting it. spawnSync builds its options as

options = {
  __proto__: null,
  maxBuffer: MAX_BUFFER,      // 1 MiB
  ...normalizeSpawnArguments(file, args, options),
};

The spread carries an own maxBuffer property whose value is undefined, which overwrites the 1 MiB default. The result is not "1 MiB" and not "the caller's value" — it is no limit at all.

So #11 and #151 are genuinely fixed on main, but not by the size gate that PR #179 added, and not by anything named in that PR. They are fixed because a property that looks like an inert pass-through happens to delete Node's default. Nothing documents this and no test covers it.

Consequence 1: one refactor away from reopening #11 and #151

Every shape a reader would consider equivalent restores the 1 MiB cap:

maxBuffer: options.maxBuffer ?? MAX,                            // different
...(options.maxBuffer !== undefined && { maxBuffer: options.maxBuffer }),  // different
// or just dropping the line as "it does nothing"

Same repo, same review, only that line changed:

upstream HEAD  (maxBuffer: options.maxBuffer) -> OK   inputMode=self-collect diffBytes=0
same logic, key omitted when unset            -> ENOBUFS  spawnSync git ENOBUFS

Consequence 2: the reads that are unbounded are the ones the gate never measures

PR #179's gate measures only the diff, and only to decide inline-diff vs self-collect. These run on every review, before and outside the gate, with no ceiling:

line command
git.mjs:123-125 diff --cached --name-only, diff --name-only, ls-files --others --exclude-standard
git.mjs:227 status --short --untracked-files=all
git.mjs:266-267 diff --name-only <range>, log --oneline --decorate <range>

--untracked-files=all expands every file under every untracked directory, so a repo with an unignored node_modules/, build/ or coverage output produces megabytes of path names while the diff itself is empty. Today that is read into a JS string with no limit; after any of the refactors above it is ENOBUFS. Neither is the intended behaviour, and per #151 an ENOBUFS from binaryAvailable surfaces as the misleading "Codex CLI is not installed".

Repro

git init repro && cd repro
git commit -qm init --allow-empty
mkdir -p build/assets
python3 -c "
import os
for i in range(14000):
    open('build/assets/generated_component_with_a_reasonably_long_file_name_%05d.chunk.js' % i, 'w').write('x')
"
git status --short --untracked-files=all | wc -c   # 1176000 -> over Node's 1 MiB default
git diff --binary | wc -c                          # 0       -> the measured gate sees nothing

Then collectReviewContext(cwd, { mode: "working-tree" }) buffers ~1.15 MB of path names on main, and throws ENOBUFS under any of the equivalent-looking rewrites.

Suggested fix

Give the pass-through an explicit, generous default so the ceiling is stated rather than inherited from a spread quirk:

// Node defaults maxBuffer to 1 MiB, which ENOBUFS on any git output larger than that.
maxBuffer: options.maxBuffer ?? 256 * 1024 * 1024,

measureGitOutputBytes still passes its own small bound, so the inline-diff / self-collect gate is unaffected. A regression test asserting that a >1 MiB git status survives runCommand would pin the behaviour that #11 and #151 currently depend on implicitly.

Environment

  • codex-plugin-cc: main @ db52e28 (1.0.6); originally hit on 1.0.2, which had no maxBuffer line at all
  • Node.js: v24.12.0 and v25.9.0 (identical results)
  • macOS arm64

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions