Skip to content

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

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

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

Conversation

@unbraind

@unbraind unbraind commented Jul 24, 2026 •

Copy link
Copy Markdown
Owner

Problem

readPmItems() — the documented safe read pattern every demo reuses 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: 1,054,002 bytes — over the cap, failing today.

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

Because this path is contractually never-throw (return [] on any failure), a read failure was indistinguishable from an empty workspace — every demo would show no items and look correct. The cap fix is paired with a stderr line so the cause is visible without breaking the contract.

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-starter-y8hb — 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

Capped pm item reads at 64 MiB with a per‑call PM_JSON_MAX_BUFFER override and clearer errors so large or failing workspaces don’t look empty. Malformed overrides now fall back to the default instead of imposing tiny caps.

  • Bug Fixes
    • Resolve PM_JSON_MAX_BUFFER on every readPmItems call; reject invalid/non‑positive values with Number() + isSafeInteger; overrun message names the actual byte limit and points to the env var. Matches sibling packages and resolves pm-starter-y8hb.
    • Keep the never‑throw [] contract but log ENOBUFS and non‑zero exits (exit code + stderr); restored the safe‑read JSDoc in the emitted d.ts.
    • Added smoke tests for a forced 64‑byte cap and malformed overrides; disabled jshint on generated dist/ in Hound to avoid ES5 lint noise.

Written for commit 7fdb919. Summary will update on new commits.

Review in cubic

…fault

readPmItems (the documented safe-read demo pattern) 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 1054002 bytes (ENOBUFS today).

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-starter-y8hb (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.

Comment thread dist/index.js Outdated
Comment thread dist/index.js Outdated
Comment thread dist/index.js
Comment thread dist/index.js Outdated
Comment thread dist/index.js Outdated
Comment thread dist/index.js Outdated
Comment thread dist/index.js
Comment thread dist/index.js Outdated

@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: dedbc5ea-5e66-40da-a60e-c4521713a94f

📥 Commits

Reviewing files that changed from the base of the PR and between 79084e4 and 7fdb919.

⛔ 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 (6)
  • .agents/pm/history/pm-starter-y8hb.jsonl
  • .agents/pm/issues/pm-starter-y8hb.toon
  • .hound.yml
  • 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: Starting a full review of #33.

✅ 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.

Hound posted 8 inline comments on this PR, all on the generated `dist/index.js`
and all artifacts of jshint's default ES5 profile: "'const' is available in ES6
(use 'esversion: 6')", "'template literal syntax' is only available in ES6", and
style opinions about line breaks in emitted code.

The package targets ES2022 on Node 22 and `dist/` is tsc output committed so the
extension can be installed straight from the repo, so none of it is actionable —
it just buries real findings under boilerplate. pm-web already carries the same
config for the same reason.

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

Copy link
Copy Markdown
Owner Author

Bot-feedback triage — round 1

Hound — 8 inline comments, all on generated dist/index.js, all jshint-ES5-profile artifacts (const/template literals flagged as ES6-only, plus line-break style in emitted code). All 8 downvoted as non-actionable, with the reasoning in-thread. Fixed at the source rather than dismissed: added .hound.yml disabling jshint here, matching what pm-web already carries. Hound's own check was SUCCESS — these were advisory.

Sourcery — weekly 500k diff-character rate limit, no review performed.

CodeRabbit — check SUCCESS, no findings.

Gemini — sunset, no review.

Re-requesting review on the new head:

@greptileai review

@coderabbitai full review

@sourcery-ai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

@unbraind: Starting a fresh full review of the updated PR head.

✅ 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 27 minutes.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR raises the pm item-read buffer limit and improves failure diagnostics.

  • Adds a per-call PM_JSON_MAX_BUFFER override with a 64 MiB default and strict validation.
  • Reports buffer overruns and nonzero pm exits while preserving the never-throw empty-array contract.
  • Adds smoke coverage for overruns and malformed overrides and updates generated distribution artifacts.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failures remain.

T-Rex T-Rex Logs

What T-Rex did

  • Reviewed the pre-proof condition by inspecting the pm-read-buffer-01-before.log to confirm the Node spawn buffering caused the failure after 1,114,112 bytes.
  • Ran the reproducible harness pm-read-buffer-harness.mjs to reproduce the read-buffer behavior and prepare for validation.
  • Validated the post-proof outcome by inspecting pm-read-buffer-02-after.log, which shows successful compiled-runtime reading and both override paths.
  • Ran the focused tests described in pm-read-focused-tests.log and confirmed 2/2 tests passed.
  • Verified the TypeScript build completed successfully as shown in pm-read-build.log.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
index.ts Adds the configurable read-buffer limit and explicit failure diagnostics while preserving the documented API contract.
test/smoke.test.ts Adds sequential, cleanup-safe integration coverage for buffer overruns and invalid environment overrides.
dist/index.js Generated runtime output is consistent with the TypeScript source changes.
dist/index.d.ts.map Generated declaration mappings reflect the source-line movement while the exported declaration documentation remains intact.

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

Comment thread index.ts Outdated
Comment thread index.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

The PR raises readPmItems subprocess capacity and adds explicit ENOBUFS diagnostics.

  • Sets a 64 MiB spawnSync buffer for complete item-list JSON reads.
  • Logs subprocess errors while retaining the existing never-throw, empty-array fallback.
  • Regenerates distribution artifacts and records the fix in project metadata and the changelog.
  • Adds Hound configuration disabling JSHint for generated JavaScript.

Confidence Score: 4/5

The PR appears safe to merge, with two non-blocking documentation and diagnostic-actionability issues worth correcting.

The 64 MiB buffer addresses the reported read failure, but its overrun message advertises unavailable controls and the source placement removes useful documentation from the generated public declaration.

index.ts, dist/index.d.ts

T-Rex T-Rex Logs

What T-Rex did

  • Before the change, the default-buffer run ended with status: null and ENOBUFS and produced unparseable truncated output.
  • After the change, readPmItems returned exactly one item, preserving its 1,500,000-byte body and the terminal complete marker.
  • The harness completed with a clean exit code of 0, confirming the test finished without errors.
  • Artifacts were reviewed to confirm the payload size and completion marker, including the 1.5 MB body artifact and the accompanying validation scripts.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
index.ts Adds the larger read buffer and error diagnostic; the remediation text is not actionable through the exposed API, and the insertion detaches the public function's JSDoc.
dist/index.js Regenerates runtime output with the source buffer and diagnostic changes.
dist/index.d.ts Regeneration drops the existing readPmItems API documentation because its source JSDoc is no longer attached to the function.
.hound.yml Disables JSHint to avoid ES5-profile warnings on committed generated ES2022 output.
CHANGELOG.md Records the item-read buffer fix in the unreleased changelog.
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:154-157
**Diagnostic advertises unavailable controls**

The ENOBUFS message tells users to narrow the operation or raise `PM_JSON_MAX_BUFFER`, but `readPmItems` hardcodes the full operation and the buffer is a private constant. Users therefore cannot apply either advertised remedy without modifying the package source.

### Issue 2 of 2
index.ts:140-145
**Buffer comments detach API documentation**

Inserting the buffer declarations between the JSDoc and `readPmItems` detaches the documentation from the exported function, so the generated declaration no longer describes its never-throw and empty-array behavior.

Reviews (2): Last reviewed commit: "chore(ci): disable jshint for generated ..." | Re-trigger Greptile

Comment thread index.ts Outdated
Comment thread index.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

The PR raises readPmItems's synchronous output limit to 64 MiB and reports spawn errors while preserving its empty-array failure contract.

  • Adds the corresponding generated distribution artifacts.
  • Records and closes the tracker issue describing the original buffer ceiling.
  • Disables Hound's JSHint analysis of generated ES2022 JavaScript.
  • Updates the changelog.

Confidence Score: 4/5

The change is safe to merge, with non-blocking API documentation and test-coverage issues to address.

The runtime buffer fix is aligned across source and generated output, but the inserted declarations remove readPmItems documentation from the shipped types and the new buffer-error behavior lacks focused regression coverage.

index.ts, dist/index.d.ts

T-Rex T-Rex Logs

What T-Rex did

  • Compared the before-change large-output fixture and observed that it produced 0 items before the buffer change.
  • Validated that the post-change built package parsed the complete payload and all assertions passed.
  • Verified that the stub and reusable harness were included as artifacts and that no tracked repository files were modified.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
index.ts Adds the 64 MiB buffer and spawn-error diagnostic, but separates the exported function from its public JSDoc and leaves the new failure branch untested.
dist/index.js Generated runtime output matches the source implementation of the increased buffer and error handling.
dist/index.d.ts Regenerated declarations omit readPmItems's previously shipped contract documentation.
.hound.yml Disables JSHint comments that targeted generated ES2022 JavaScript rather than handwritten source.
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:141-159
**Preserve the exported API docs**

The new constant and helper separate `readPmItems` from its JSDoc, so TypeScript omits the function's never-throw and empty-array-on-failure contract from the shipped declaration and IDE documentation. Move the JSDoc back alongside the export.

### Issue 2 of 2
index.ts:166-172
**Cover the new buffer branch**

The new `maxBuffer` and `result.error` behavior has no test coverage, including the `ENOBUFS` diagnostic that motivates this change. Add a mocked `spawnSync` case that verifies the 64 MiB option, diagnostic, and empty-array return so this failure contract cannot silently regress.

Reviews (2): Last reviewed commit: "chore(ci): disable jshint for generated ..." | Re-trigger Greptile

Comment thread index.ts
Comment thread index.ts Outdated
unbraind and others added 3 commits July 24, 2026 22:34
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>
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>
…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

Round 2 findings addressed → pushed 7fdb919

bot finding severity verdict action
Greptile parseInt("64MiB") → 64 imposes a 64-byte cap while claiming an invalid-value fallback P1 confirmed — bug in my own fix strict Number() + isSafeInteger, 7-case test
Greptile overrun branch had no test coverage P2 valid real-workspace test forcing the overrun via the env override
Greptile ENOBUFS message prescribed raising a private constant P2 valid cap now env-overridable, resolved per call
Greptile inserted constant orphaned readPmItems' JSDoc → dropped from dist/index.d.ts P2 confirmed in generated output block moved above the JSDoc
Hound 8 comments on generated dist/index.js (jshint ES5 profile) — non-actionable downvoted + .hound.yml added so it stops recurring
CodeRabbit / Sourcery / Gemini no findings / rate-limited / sunset — — voted, context posted

Every Greptile finding this round was correct, including two where the fix needed fixing. Test count here went 7 → 9, both new tests covering the failure contract rather than the happy path.

@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 11 minutes and 42 seconds before sending another message.

@unbraind

Copy link
Copy Markdown
Owner Author

Final round — both notes in the Greptile summary are already addressed on this head (7fdb919)

The summary flagged "non-blocking API documentation and test-coverage issues"; both were fixed in 01cb689 / 7fdb919 after that review ran. Proof from the current head:

Documentation restored in the shipped types (dist/index.d.ts):

/**
 * Safely read all items from the workspace by shelling out to `pm`. Returns an
 * empty array on any failure so demos never throw at activation/read time.
 * This is the SAFE read pattern every demo reuses.
 */
export declare function readPmItems(pmRoot: string): Array<Record<string, any>>;

Coverage added — 7 → 9 tests, both new ones on the failure contract rather than the happy path: an overrun forced through the env override against a real pm init workspace must return the documented [] and report the cause; and a malformed override (64MiB, 64 MB, abc, -1, 0, 6.5, ``) must fall back to the default rather than imposing a tiny cap.

On T-Rex

Worth calling out: T-Rex executed the before/after and independently reproduced the defect — "observed that it produced 0 items before the buffer change", then confirmed the post-change build parses the complete payload. That is a materially stronger signal than a static read, and it matched the measurement that motivated this sweep (1,054,002 bytes against a real 443-item workspace). Same on pm-ts-starter#33, where T-Rex captured status=null, error_code=ENOBUFS and truncated stdout pre-change. Please keep doing that on this kind of change.

All bot comments on this PR are read, voted and answered in-thread. Merging.

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