[PR #5673] [MERGED] test(cli): keep IPC trace input under the Linux spawn limit #7028

Closed
opened 2026-11-01 21:44:49 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5673
Author: @morluto
Created: 10/20/2026
Status: ✅ Merged
Merged: 10/20/2026
Merged by: @morluto

Base: main ← Head: cursor/fix-ipc-trace-e2big-a55b


📝 Commits (4)

  • 4a31a01 test(cli): keep IPC trace input under the Linux spawn limit
  • 7bfc2a5 Merge remote-tracking branch 'origin/main' into cursor/fix-ipc-trace-e2big-a55b
  • e975bcb test(process): size the spawn ceiling from ARG_MAX and MAX_ARG_STRLEN
  • 05f4b93 style(test): format files rejected by oxfmt in static checks

📊 Changes

5 files changed (+273 additions, -11 deletions)

View changed files

📝 tests/acceptance/applications/electronIpcListenerCallsiteCli.test.ts (+20 -4)
➕ tests/boundary/process/spawnArgumentLimit.test.ts (+181 -0)
📝 tests/composition/reverseEngineeringAdmission.test.ts (+4 -4)
📝 tests/support/cli/cliFixture.ts (+2 -3)
➕ tests/support/process/spawnArgumentLimit.ts (+66 -0)

📄 Description

Summary

electronIpcListenerCallsiteCli dies on GitHub Actions with spawn E2BIG while starting trace-application-feature. The trace request is one argv string of the full application Evidence document (~292 KiB). Linux rejects a single argv or env string at MAX_ARG_STRLEN (32 pages, 131071 bytes on the 4 KiB Actions runners) before the process starts. The CLI already reads that JSON from a file. This keeps the file path, requires the spawned argv to fit the per-string ceiling, and locks both that ceiling and the aggregate ARG_MAX budget with a process-registry regression that does not need Electron.

Human owner: @morluto

Cited failures, both on the inline-JSON revision:

Both stacks are processFixture spawn ← cliFixture.run ← the CLI case at the trace invocation. The earlier analyze-javascript-application spawn uses the same minimal environment and succeeds, so this is the argument string, not inherited env or NODE_OPTIONS.

Problem and expected behavior

createTestCli spawns node scripts/rea.mjs with PATH only (plus the Windows temp roots). Placing JSON.stringify({ application, seed, direction }) in argv makes one argument of 292118 bytes for this fixture. On this host getconf PAGE_SIZE is 4096 and getconf ARG_MAX is 2097152. A 131071-byte argument exits 0; 131072 bytes throws spawn E2BIG synchronously. With RLIMIT_STACK of 512 KiB, ARG_MAX drops to 131072 and a payload at the single-string ceiling still fails because the filename, other argv, environment, NULs, and pointer table consume the rest. The trace command must still count the exposed pong listener callsites and return matched_seeds: 1.

Change and scope

The acceptance CLI case writes the trace request to trace-input.json and passes that path. It requires the serialized document to stay past the 4 KiB-page ceiling and the actual argv to fit under it, so putting the document back on argv fails on every host.

acceptedSpawnPayloadBytes takes the minimum of the per-string ceiling and the bytes left in ARG_MAX after the filename (copied before argv), the other argv strings, each KEY=value environment string, their NULs, and one native pointer per argv and env string. The registry test spawns that derived size. When the fixed image already fills ARG_MAX, it skips the success spawn. One byte past the per-string ceiling, and one byte past a tighter aggregate budget, must throw E2BIG.

check:ci:fast failed oxfmt --check on tests/support/cli/cliFixture.ts and, on the merge with main, tests/composition/reverseEngineeringAdmission.test.ts. Those two files are formatted and nothing else.

Contribution scope: not applicable. Test-only. No new provider, target execution, injection, mutation, or protection recovery.

Intentionally left alone: production CLI parsing, Evidence size, and a harness-wide argv guard in createTestCli (other tests pass intentional inline JSON).

Contract and boundary impact

  • Semantic owner and earliest changed stage: test process spawn. The failure is execve of the compiled CLI, before command dispatch.
  • Contribution scope fit and linked maintainer decision: ordinary test fix within the existing CLI JSON file input. Maintainer scope decision not applicable.
  • CLI and MCP/tool-catalog contract: none. parseCliJsonInput already reads a path when the argument is not JSON.
  • Provider, bridge, target-format, or platform compatibility: Linux single-argument limit and aggregate ARG_MAX. The shared budget is the 4 KiB-page ceiling Actions uses. Larger page sizes still check 32 * pageSize - 1. The kernel spawn case is skipped off Linux. Pointer width is 4 bytes for arm, ia32, mips, and mipsel, and 8 otherwise.
  • Evidence, artifact, provenance, or reconstruction contract: none.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: tests/acceptance/applications/electronIpcListenerCallsiteCli.test.ts, tests/boundary/process/spawnArgumentLimit.test.ts, tests/support/process/spawnArgumentLimit.ts. Format-only: tests/support/cli/cliFixture.ts, tests/composition/reverseEngineeringAdmission.test.ts.
  • Base reproduction: on Linux Node v24.18.0, the fixture trace argument is 292118 bytes and spawn throws E2BIG. Length 131071 exits 0; length 131072 throws E2BIG. The same document passed as a file path exits 0 with matched_seeds: 1. Re-introducing that string as an argv element fails assertSpawnArgumentsWithinLimit before spawn.
  • Aggregate headroom: under prlimit --stack=524288, getconf ARG_MAX is 131072. The derived payload (130532 bytes for this image) exits 0 and one more byte throws E2BIG. The same vitest file passes under that stack limit.
  • Static check: job https://github.com/morluto/rea/actions/runs/39490671462/job/118863805852 failed in npm run check:ci:fast because oxfmt --check . reported those two files. format:check is clean after the format commit.
  • User-visible CLI/MCP output (if applicable): none.
  • Remaining proof gaps: the updated tree has not yet finished a new GitHub Actions run.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • Direct spawn probe — 292118-byte trace argument and a 131072-byte argument throw spawn E2BIG; 131071 bytes exits 0. File-path trace-application-feature exits 0 with summary.matched_seeds 1.
  • Reduced stack — prlimit --stack=524288 makes ARG_MAX 131072. Payload 130532 exits 0; 130533 throws E2BIG. vitest run tests/boundary/process/spawnArgumentLimit.test.ts --project process-boundary under that limit: 4 passed.
  • npm run test:focused -- tests/boundary/process/spawnArgumentLimit.test.ts tests/acceptance/applications/electronIpcListenerCallsiteCli.test.ts — 6 passed (CLI and stdio MCP acceptance, plus the four limit cases).
  • tsc --noEmit — exit 0.
  • oxfmt --check . — clean.
  • oxlint on the touched files — clean.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: Linux spawn ceiling measured on this host (4 KiB pages), including a 512 KiB stack. No Electron binary, Hopper, or Ghidra. The listener fixture is static source analysis.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: test processes only. The regression uses PATH and, on Windows, the same temp roots as the CLI fixture.

Author readiness

Human owner: @morluto

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.
Open in Web Open in Cursor 

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/5673 **Author:** [@morluto](https://github.com/morluto) **Created:** 10/20/2026 **Status:** ✅ Merged **Merged:** 10/20/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `cursor/fix-ipc-trace-e2big-a55b` --- ### 📝 Commits (4) - [`4a31a01`](https://github.com/morluto/rea/commit/4a31a01e452f5b0c3b9036a6cd54ae81486eb668) test(cli): keep IPC trace input under the Linux spawn limit - [`7bfc2a5`](https://github.com/morluto/rea/commit/7bfc2a5ebf41e4b846715edbba9d291950222dd9) Merge remote-tracking branch 'origin/main' into cursor/fix-ipc-trace-e2big-a55b - [`e975bcb`](https://github.com/morluto/rea/commit/e975bcb72b1e73dc8cdaca5b550a177ce6155852) test(process): size the spawn ceiling from ARG_MAX and MAX_ARG_STRLEN - [`05f4b93`](https://github.com/morluto/rea/commit/05f4b93e1653a6889e34a6ac4c7c1baecb85ef20) style(test): format files rejected by oxfmt in static checks ### 📊 Changes **5 files changed** (+273 additions, -11 deletions) <details> <summary>View changed files</summary> 📝 `tests/acceptance/applications/electronIpcListenerCallsiteCli.test.ts` (+20 -4) ➕ `tests/boundary/process/spawnArgumentLimit.test.ts` (+181 -0) 📝 `tests/composition/reverseEngineeringAdmission.test.ts` (+4 -4) 📝 `tests/support/cli/cliFixture.ts` (+2 -3) ➕ `tests/support/process/spawnArgumentLimit.ts` (+66 -0) </details> ### 📄 Description <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Summary `electronIpcListenerCallsiteCli` dies on GitHub Actions with `spawn E2BIG` while starting `trace-application-feature`. The trace request is one argv string of the full application Evidence document (~292 KiB). Linux rejects a single argv or env string at `MAX_ARG_STRLEN` (32 pages, 131071 bytes on the 4 KiB Actions runners) before the process starts. The CLI already reads that JSON from a file. This keeps the file path, requires the spawned argv to fit the per-string ceiling, and locks both that ceiling and the aggregate `ARG_MAX` budget with a process-registry regression that does not need Electron. Human owner: @morluto Cited failures, both on the inline-JSON revision: - main `1629266e3` run https://github.com/morluto/rea/actions/runs/39471426969 (shard 2/5) - #5652 `c70984e55` run https://github.com/morluto/rea/actions/runs/39475793479 (shard 2/8) Both stacks are `processFixture` spawn ← `cliFixture.run` ← the CLI case at the trace invocation. The earlier `analyze-javascript-application` spawn uses the same minimal environment and succeeds, so this is the argument string, not inherited env or `NODE_OPTIONS`. ## Problem and expected behavior `createTestCli` spawns `node scripts/rea.mjs` with `PATH` only (plus the Windows temp roots). Placing `JSON.stringify({ application, seed, direction })` in argv makes one argument of 292118 bytes for this fixture. On this host `getconf PAGE_SIZE` is 4096 and `getconf ARG_MAX` is 2097152. A 131071-byte argument exits 0; 131072 bytes throws `spawn E2BIG` synchronously. With `RLIMIT_STACK` of 512 KiB, `ARG_MAX` drops to 131072 and a payload at the single-string ceiling still fails because the filename, other argv, environment, NULs, and pointer table consume the rest. The trace command must still count the exposed `pong` listener callsites and return `matched_seeds: 1`. ## Change and scope The acceptance CLI case writes the trace request to `trace-input.json` and passes that path. It requires the serialized document to stay past the 4 KiB-page ceiling and the actual argv to fit under it, so putting the document back on argv fails on every host. `acceptedSpawnPayloadBytes` takes the minimum of the per-string ceiling and the bytes left in `ARG_MAX` after the filename (copied before argv), the other argv strings, each `KEY=value` environment string, their NULs, and one native pointer per argv and env string. The registry test spawns that derived size. When the fixed image already fills `ARG_MAX`, it skips the success spawn. One byte past the per-string ceiling, and one byte past a tighter aggregate budget, must throw `E2BIG`. `check:ci:fast` failed `oxfmt --check` on `tests/support/cli/cliFixture.ts` and, on the merge with main, `tests/composition/reverseEngineeringAdmission.test.ts`. Those two files are formatted and nothing else. Contribution scope: not applicable. Test-only. No new provider, target execution, injection, mutation, or protection recovery. Intentionally left alone: production CLI parsing, Evidence size, and a harness-wide argv guard in `createTestCli` (other tests pass intentional inline JSON). ## Contract and boundary impact - Semantic owner and earliest changed stage: test process spawn. The failure is `execve` of the compiled CLI, before command dispatch. - Contribution scope fit and linked maintainer decision: ordinary test fix within the existing CLI JSON file input. Maintainer scope decision not applicable. - CLI and MCP/tool-catalog contract: none. `parseCliJsonInput` already reads a path when the argument is not JSON. - Provider, bridge, target-format, or platform compatibility: Linux single-argument limit and aggregate `ARG_MAX`. The shared budget is the 4 KiB-page ceiling Actions uses. Larger page sizes still check `32 * pageSize - 1`. The kernel spawn case is skipped off Linux. Pointer width is 4 bytes for arm, ia32, mips, and mipsel, and 8 otherwise. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [ ] Read existing files or captured data. - [x] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `tests/acceptance/applications/electronIpcListenerCallsiteCli.test.ts`, `tests/boundary/process/spawnArgumentLimit.test.ts`, `tests/support/process/spawnArgumentLimit.ts`. Format-only: `tests/support/cli/cliFixture.ts`, `tests/composition/reverseEngineeringAdmission.test.ts`. - Base reproduction: on Linux Node v24.18.0, the fixture trace argument is 292118 bytes and `spawn` throws `E2BIG`. Length 131071 exits 0; length 131072 throws `E2BIG`. The same document passed as a file path exits 0 with `matched_seeds: 1`. Re-introducing that string as an argv element fails `assertSpawnArgumentsWithinLimit` before spawn. - Aggregate headroom: under `prlimit --stack=524288`, `getconf ARG_MAX` is 131072. The derived payload (130532 bytes for this image) exits 0 and one more byte throws `E2BIG`. The same vitest file passes under that stack limit. - Static check: job https://github.com/morluto/rea/actions/runs/39490671462/job/118863805852 failed in `npm run check:ci:fast` because `oxfmt --check .` reported those two files. `format:check` is clean after the format commit. - User-visible CLI/MCP output (if applicable): none. - Remaining proof gaps: the updated tree has not yet finished a new GitHub Actions run. For evidence-bearing changes: - [ ] Observed, derived, and inferred claims remain distinguishable. - [ ] Artifact identity, source provenance, and failed attempts remain preserved. - [ ] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - Direct spawn probe — 292118-byte trace argument and a 131072-byte argument throw `spawn E2BIG`; 131071 bytes exits 0. File-path `trace-application-feature` exits 0 with `summary.matched_seeds` 1. - Reduced stack — `prlimit --stack=524288` makes `ARG_MAX` 131072. Payload 130532 exits 0; 130533 throws `E2BIG`. `vitest run tests/boundary/process/spawnArgumentLimit.test.ts --project process-boundary` under that limit: 4 passed. - `npm run test:focused -- tests/boundary/process/spawnArgumentLimit.test.ts tests/acceptance/applications/electronIpcListenerCallsiteCli.test.ts` — 6 passed (CLI and stdio MCP acceptance, plus the four limit cases). - `tsc --noEmit` — exit 0. - `oxfmt --check .` — clean. - `oxlint` on the touched files — clean. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: Linux spawn ceiling measured on this host (4 KiB pages), including a 512 KiB stack. No Electron binary, Hopper, or Ghidra. The listener fixture is static source analysis. - Package or release metadata impact: none. - Security, privacy, process, or containment review: test processes only. The regression uses `PATH` and, on Windows, the same temp roots as the CLI fixture. ## Author readiness <!-- The human owner completes these declarations before requesting review. The PR author is the owner by default. A different owner must explicitly accept ownership on this PR. AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA. Draft PRs may leave them unchecked. These declarations record initial readiness when opening the PR for review; later pushes do not require a new human sign-off. Keep the description and validation evidence current as the work changes. See CONTRIBUTING.md#pull-request-ownership-and-readiness. --> Human owner: @morluto - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge <!-- Draft PRs may leave pending items unchecked. Resolve material scope and authority questions before integration. --> - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-bf2fc701-3414-56a2-950e-4c2e0f3ea55b?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-bf2fc701-3414-56a2-950e-4c2e0f3ea55b&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:44:49 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#7028
No description provided.