Skip to content

fix: cap pm item reads at 64 MiB instead of Node's 1 MiB spawnSync default - #33

Merged
unbraind merged 4 commits into
mainfrom
fix/pm-read-buffer-1mib-ceiling
Jul 24, 2026
Merged

unbraind merged 4 commits into
mainfrom
fix/pm-read-buffer-1mib-ceiling

Conversation

@unbraind

@unbraind unbraind commented Jul 24, 2026 •

Copy link
Copy Markdown
Owner

Problem

the ts-starter-prefix search provider spawns the pm CLI to read items but passes no maxBuffer, so Node's 1 MiB spawnSync default applies.

Measured with this package's exact argv against a real 443-item pm workspace: 985,089 bytes — 6% under the cap, latent.

Past the cap the child is killed and the result comes back as:

status: null   signal: SIGTERM   error.code: ENOBUFS   stderr: ""

Because stderr is empty, the failure surfaced with nothing to diagnose. And at larger sizes stdout is genuinely truncated mid-document — verified by forcing a 100 KiB cap on the same workspace:

JSON.parse -> Unterminated string in JSON at position 130829

This is a search provider, where {results: []} is a perfectly ordinary answer — so an unreadable workspace looked exactly like no matches. The cap fix is paired with a stderr line naming the read failure.

This was a straggler: pm-changelog, pm-context, pm-brief, pm-graph and pm-linear already cap their pm reads explicitly. A fleet-wide audit this session found 8 packages that did not.

Change

  • cap the pm read at 64 MiB, matching the sibling packages
  • report an ENOBUFS overrun explicitly — naming the limit and the remedy — instead of an unexplained failure

Verification

Same argv, same real 443-item workspace:

read buffer result
Node default (1 MiB) status: null, ENOBUFS, empty stderr
64 MiB (this PR) status: 0, complete payload

Typecheck and the full test suite pass; changelog:check green.

pm items

item status
pm-ts-starter-bftc — pm item reads are capped at Node's 1 MiB spawnSync default, so a mature tracker fails with no diagnosis closed

Companion-tracked fleet sweep; the same fix landed in pm-github (unbraind/pm-github#13), where it additionally unblocked --atomic --dry-run.

🤖 Generated with Claude Code


Summary by cubic

Caps the pm JSON read buffer to 64 MiB (configurable) and resolve it per call so env overrides always apply. Prevents Node’s 1 MiB spawnSync limit from killing large workspaces and makes read failures visible instead of looking like empty results.

  • Bug Fixes
    • Cap pm list-all --json via PM_JSON_MAX_BUFFER (default 64 MiB), resolved per call; reject malformed or non‑integer overrides (e.g. 64MiB) so they fall back to the default instead of silently imposing tiny caps.
    • Report real read failures: log ENOBUFS overruns with the active limit and non‑zero pm exits with stderr while keeping the empty‑result contract; restored the public “never throws” JSDoc and added a smoke test that enforces the overrun reporting.

Written for commit 2fd437d. Summary will update on new commits.

Review in cubic

…fault

the ts-starter-prefix search provider spawned the pm CLI without a maxBuffer, so Node's 1 MiB default
applied. Measured against a real 443-item pm workspace, this package's exact
argv returns 985089 bytes — 6% under the cap (latent).

Past the cap the child is killed with status null, signal SIGTERM, error.code
ENOBUFS and EMPTY stderr — so the failure surfaced with nothing to diagnose. At
larger sizes stdout is genuinely truncated mid-document (a 100 KiB cap yields
'Unterminated string in JSON at position 130829').

Capped at 64 MiB, matching pm-changelog / pm-context / pm-brief / pm-graph /
pm-linear, which already guard this. An overrun now names itself instead of
surfacing as an unexplained failure.

Item: pm-ts-starter-bftc (closed)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@unbraind

Copy link
Copy Markdown
Owner Author

@greptileai review

/gemini review

@coderabbitai full review

@sourcery-ai review

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

1 similar comment
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@unbraind, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aa3d14df-69d3-4627-a82d-58af3321eb29

📥 Commits

Reviewing files that changed from the base of the PR and between 7eb36db and 2fd437d.

⛔ Files ignored due to path filters (3)
  • dist/index.d.ts.map is excluded by !**/dist/**, !**/*.map
  • dist/index.js is excluded by !**/dist/**
  • dist/index.js.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (5)
  • .agents/pm/history/pm-ts-starter-bftc.jsonl
  • .agents/pm/issues/pm-ts-starter-bftc.toon
  • CHANGELOG.md
  • index.ts
  • test/smoke.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pm-read-buffer-1mib-ceiling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

@unbraind: I’ll perform a full review of #33, including the complete diff and relevant surrounding code.

✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 30 minutes.

@unbraind

Copy link
Copy Markdown
Owner Author

Bot-feedback triage — round 1

CodeRabbit — check SUCCESS, no findings. Extra context so a re-review can be sharper: this is a one-constant change with a measured failure behind it, not a defensive tweak. The exact argv this package uses returns over 1 MiB (or within 6% of it) against a real 443-item pm workspace, and past Node's default spawnSync cap the child is killed with status: null, signal: SIGTERM, error.code: ENOBUFS and empty stderr — so the pre-existing if (result.status !== 0) throw result.stderr || "...failed" reported an unexplained failure. At larger sizes stdout truncates mid-JSON (forcing a 100 KiB cap on the same workspace yields Unterminated string in JSON at position 130829). The highest-value thing to look for here is any other unguarded child-process read of pm output in this package that the audit's grep missed.

Sourcery — weekly 500k diff-character rate limit, no review performed; downvote is on the limit notice only, not on Sourcery's judgment.

Gemini — sunset notice, no review; downvoted as non-actionable.

cubic — no findings surfaced.

@greptileai review

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@unbraind have exceeded the limit for the number of chat messages per hour. Please wait 27 minutes and 41 seconds before sending another message.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Greptile Summary

Caps pm list-all --json reads at a configurable 64 MiB default and reports buffer overruns and nonzero CLI exits while preserving the search provider’s empty-result contract.

  • Resolves PM_JSON_MAX_BUFFER per call and rejects malformed, non-positive, or unsafe integer values.
  • Updates generated distribution artifacts and adds smoke coverage for buffer overruns.
  • Records and closes the corresponding pm issue and updates the changelog.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failures remain in the fixes associated with the previous review threads.

T-Rex T-Rex Logs

What T-Rex did

  • In the pre-change state, the environment override was ignored and the real item was read and returned without an overrun diagnostic.
  • In the post-change state, the current compiled extension returned empty results and logged the message pm output exceeded the 64 byte read buffer with guidance to raise PM_JSON_MAX_BUFFER.
  • The targeted test exited with status 0, reporting 1 pass and 0 failures.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
index.ts Adds safe per-call buffer-limit resolution and visible diagnostics for failed search-provider reads.
test/smoke.test.ts Adds an integration smoke test confirming overruns remain non-throwing and emit a diagnostic.
dist/index.js Regenerates the distributed implementation to match the TypeScript source.

Reviews (7): Last reviewed commit: "fix: reject malformed read-buffer overri..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR raises the search provider's pm read limit and improves ENOBUFS diagnostics.

  • Sets the synchronous pm list-all --json output cap to 64 MiB.
  • Logs spawn failures before returning an empty search result.
  • Regenerates distribution artifacts and records the change in project tracking and changelog files.

Confidence Score: 3/5

The incomplete handling of nonzero pm exits should be fixed before merging because unreadable workspaces can still be presented as empty search results.

The larger buffer is propagated correctly to the runtime artifact, but the new diagnostic branch handles only spawn-level errors; normal nonzero CLI exits still discard stderr and return an indistinguishable empty result set.

index.ts, dist/index.js

T-Rex T-Rex Logs

What T-Rex did

  • Reproduced the nonzero read failure by running the compiled extension with a controlled pm shim; the child exited 42, emitted a workspace-read error on stderr, and produced no spawn-level error. Verified that invoking the ts-starter-prefix provider returned an empty results array and produced zero console.error calls, confirming the nonzero CLI failure remained silent.
  • Validated the fake pm invocation received the expected argv, emitted 2,200 items, and recorded the payload size. Both runs exited 0 with assertions enabled, and index.ts and dist/index.js remained unmodified.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
index.ts Adds the 64 MiB read cap and spawn-error reporting, but nonzero CLI exits remain silent and overrun guidance names unavailable recovery controls.
dist/index.js Faithfully carries the source behavior into the package runtime entrypoint, including incomplete nonzero-exit diagnostics and non-actionable overrun guidance.
CHANGELOG.md Documents the corrected default-buffer limitation.
.agents/pm/issues/pm-ts-starter-bftc.toon Records the issue, verification, and resolution metadata.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
index.ts:643-645
**Overrun guidance is not actionable**

The ENOBUFS message tells users to narrow the operation or raise `PM_JSON_MAX_BUFFER`, but this provider always executes the full `list-all` read and the limit is a module-private constant. A workspace over 64 MiB therefore receives recovery instructions that cannot be followed through the integration.

### Issue 2 of 2
index.ts:661-663
**Nonzero read failures remain silent**

When `pm list-all` runs but exits nonzero because the workspace is unreadable or invalid, `result.error` is unset and execution reaches the unchanged silent `status !== 0` return. The provider discards `stderr` and reports an empty result set, so the diagnostic added here still leaves ordinary CLI read failures indistinguishable from no matches.

Reviews (1): Last reviewed commit: "fix: cap pm item reads at 64 MiB instead..." | Re-trigger Greptile

Comment thread index.ts Outdated
Comment thread index.ts
Greptile review findings, all confirmed:

- **P2 — the ENOBUFS diagnostic prescribed an impossible remedy.** It told the
  reader to raise the cap, but the cap was a module-private compile-time
  constant with no override, so a published-package consumer could only follow
  that advice by patching and rebuilding. The cap now reads an env var
  (`PM_JSON_MAX_BUFFER` / `PM_LIST_MAX_BUFFER`, in bytes), falling back to the
  default on any invalid or non-positive value so the guard cannot be silently
  disabled.
- **P2 (pm-starter) — inserting the constant between the existing JSDoc and
  `readPmItems` orphaned the doc comment**, dropping the "never throws / safe
  read pattern" contract from the consumer-facing `dist/index.d.ts`. Verified in
  the generated output, then moved the block above the JSDoc; the contract is
  back in `dist/index.d.ts`.
- **P1 (starters) — a genuine nonzero `pm` exit was still silent.** Only the
  `result.error` branch was hardened, so an unreadable or invalid workspace still
  returned an empty result with stderr discarded — leaving exactly the failure
  mode this change set out to remove: silence that looks like "no items" /
  "no matches". Both never-throw paths now log the exit code and stderr while
  keeping their empty-result contract.

Typecheck and full test suite green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

Caps pm list-all --json reads at 64 MiB and adds explicit diagnostics when the search provider cannot spawn or buffer the pm process.

  • Adds PM_JSON_MAX_BUFFER and passes it to spawnSync.
  • Reports ENOBUFS and other spawn errors before returning an empty search result.
  • Regenerates distribution artifacts and records the fix in project history and the changelog.

Confidence Score: 5/5

The PR appears safe to merge with no actionable correctness or security issues identified.

The changed search path raises the output limit while preserving successful parsing and explicitly diagnosing spawn failures before returning the provider’s established empty-result fallback.

T-Rex T-Rex Logs

What T-Rex did

  • The contract validation shows that before the change, the default-buffer invocation produced status=null, error_code=ENOBUFS, and captured truncated stdout.
  • After the change, dist/index.js registers ts-starter-prefix, invokes pm --path /tmp/trex-pm-root list-all --json, parses the full payload, and returns exactly two prefix-matching results.
  • Both harness runs exited successfully, and captured stderr was empty.
  • Two JSON payload artifacts were created to verify that the full payload was parsed and exactly two results were returned.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
index.ts Adds the 64 MiB read cap and explicit spawn-error diagnostics without introducing an actionable defect.
dist/index.js Correctly reflects the source-level search-provider changes in the generated runtime artifact.
CHANGELOG.md Documents the corrected buffer limit under the unreleased fixes.

Reviews (3): Last reviewed commit: "fix: cap pm item reads at 64 MiB instead..." | Re-trigger Greptile

@unbraind

Copy link
Copy Markdown
Owner Author

Round 1 findings addressed → pushed 808cee4

bot finding severity verdict action
Greptile nonzero pm exit still returned a silent empty result set P1 confirmed — my fix only closed half the gap exit code + stderr now logged, empty-result contract kept
Greptile ENOBUFS diagnostic prescribed raising a module-private constant P2 valid cap now overridable via PM_JSON_MAX_BUFFER env var
CodeRabbit — — no findings —
Sourcery weekly diff-char rate limit — not a finding downvoted the notice
Gemini sunset — not a finding downvoted

Both Greptile fixes were applied across the entire 8-package sweep, not just here — the same diagnostic text and the same never-throw pattern exist in the sibling packages.

@greptileai review

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@unbraind have exceeded the limit for the number of chat messages per hour. Please wait 22 minutes and 15 seconds before sending another message.

Follow-up to the Greptile round-2 findings.

- The cap was resolved once at module load, so the `PM_JSON_MAX_BUFFER` override
  only applied if the env var was set before the module was imported — an
  import-order dependency that also made the branch untestable. Now resolved per
  call (one integer parse against a process spawn: free), and the ENOBUFS message
  reports the limit actually in effect rather than a constant's name.
- Added coverage for the failure contract in the starter packages: a real pm
  workspace read with the cap forced to 64 bytes must return the documented empty
  result AND report the overrun on stderr. Without a test, "a read failure must
  not look like an empty workspace" was an assertion in a commit message rather
  than something the suite enforces.

Typecheck and the full test suite green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread index.ts Outdated
…eric prefix

Greptile P1: `Number.parseInt("64MiB", 10)` returns 64, so that value silently
imposed a **64-BYTE** cap — breaking every ordinary read while appearing to honor
the documented invalid-value fallback. The plausible-looking typo was the worst
case: it turned a safety guard into a guaranteed failure.

Switched to `Number()`, which rejects the whole string, plus
`Number.isSafeInteger(raw) && raw > 0`. Covered in the starter suite across
"64MiB", "64 MB", "abc", "-1", "0", "6.5" and "", each of which must fall back to
the default, while a valid explicit value is still honored.

Also narrowed the starters' overrun message: those reads are always the full
workspace, so "narrow the operation" was a dead instruction — the message now
names only the lever the reader has.

Typecheck and full test suites green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@unbraind

Copy link
Copy Markdown
Owner Author

Final round — all findings addressed, T-Rex verification acknowledged

Greptile's T-Rex independently executed the before/after here and captured exactly the failure this PR fixes — pre-change: status=null, error_code=ENOBUFS, truncated stdout; post-change: full payload parsed, provider returns exactly the two prefix-matching results, stderr empty. Confidence 5/5. That is the strongest form this review could have taken, and it corroborated the measurement that motivated the sweep (985,089 bytes today — within 6% of the 1 MiB cliff, so this package was latent rather than already failing).

Findings this round and their resolution:

finding severity action
nonzero pm exit still returned a silent empty result P1 exit code + stderr now logged, empty-result contract preserved
parseInt("64MiB") → 64 imposed a 64-byte cap P1 strict Number() + isSafeInteger, 7-case test
ENOBUFS remedy named a private constant P2 env-overridable, resolved per call
"narrow the operation" was a dead instruction for a whole-workspace read minor message now names only the available lever

Test count 8 → 10. All bot comments read, voted and answered in-thread. Merging.

@unbraind
unbraind merged commit db2d578 into main Jul 24, 2026
6 checks passed
@unbraind
unbraind deleted the fix/pm-read-buffer-1mib-ceiling branch July 24, 2026 20:54
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