[GH-ISSUE #7217] [Bug] exec and execFile report a stdio mode their spawn call never receives #4303

Closed
opened 2026-10-31 00:48:43 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @XiaoBinGan on GitHub (Oct 26, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7217

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

analyze_javascript_application reports a stdio_mode for exec and execFile from the caller's stdio option, but those two functions never pass stdio to spawn, so the value cannot take effect.

Declared vs reported, measured through the JavaScript semantic analysis:

call reported stdio_mode child's actual stdout/stderr
exec("x") default pipe
exec("x", {stdio:"ignore"}) ignore pipe
exec("x", {stdio:"inherit"}) inherit pipe
exec("x", {stdio:"pipe"}) pipe pipe
exec("x", {stdio:["ignore","pipe","pipe"]}) array pipe
exec("x", {stdio:input}) dynamic pipe
execFile("x", {stdio:"ignore"}) ignore pipe
execFile("x", [], {stdio:"inherit"}) inherit pipe

spawn and fork are unaffected: spawn("x", {stdio:"ignore"}) reports ignore and the child's streams really are null there.

Reproduction

Four independent checks on Node 24.22.1, Windows x64, against main at 9341b116e.

  1. Node's own source: require("node:child_process").execFile.toString() builds its child from spawn(file, args, {cwd, env, gid, shell, signal, uid, windowsHide, windowsVerbatimArguments}). The object passed to spawn has no stdio key, and exec only normalizes its arguments and then calls execFile.
  2. The published types: node_modules/@types/node/child_process.d.ts declares interface ExecFileOptions extends CommonOptions, Abortable, and CommonOptions has only windowsHide and timeout. stdio appears on CommonSpawnOptions / SpawnOptionsWithoutStdio, not on the exec family.
  3. Runtime probe: exec("echo hi", {stdio:"ignore"}) still delivers hi to its callback and child.stdio holds sockets, while spawn(process.execPath, ["-e",""], {stdio:"ignore"}) yields [null, null, null].
  4. REA end-to-end: analyzing a directory whose entry.mjs calls exec("echo ignored", {stdio:"ignore"}), exec("echo inherited", {stdio:"inherit"}) and execFile("tool", {stdio:"ignore"}) emits "stdio_mode":"ignore", "stdio_mode":"inherit" and "stdio_mode":"ignore".

Expected behavior

Either reported mode is what the child actually gets (pipe for the exec family), or the declared-but-ineffective option is reported as an explicit limitation rather than as the effective mode.

I am not asking for one over the other. The reason this is a question rather than a patch: de9371b85 (fix(javascript): preserve effective child-process stdio options, #4336) introduced tests/acceptance/applications/javascriptChildProcessStdioCli.test.ts, and that test currently asserts the declared value for this family — expected.set("cjs-var-argv", ["inherit", null]) for execFile("cjs-var-argv", stable, {stdio:"inherit"}). So the committed assertion and the commit's own "effective stdio" wording disagree, and which one is authoritative is a maintainer call.

Impact and scope

An analyst reading the child-process graph is told an exec call's output is discarded or inherited when Node actually pipes it and delivers it to the callback. The graph's connects-stdio relation is also marked resolved for these values, so the misread is not flagged as uncertain.

REA version

6.8.0 source checkout at 9341b116e

Runtime and platform

Windows x64, Node 24.22.1

Target and artifact context

Inline JavaScript sources; no real application and no execution of the analyzed code.

Interface

CLI (rea analyze-javascript-application), and the same JavaScriptSemanticIR reaches MCP.

Earliest failure stage

Evidence, artifacts, or reconstruction

Relevant output or evidence

src/domain/javascript/javascriptSemanticChildProcesses.ts, stdioMode(): the lookup.status === "explicit" branch returns the declared string/array before any method-specific rule, and only fork then falls back to unsetStdioMode. Nothing consults the exec family's fixed spawn option list.

Typed outcome or recovery expected

No new error. Either the mode changes to the effective one, or the ineffective declaration surfaces as an explicit limitation.

Analysis or suspected cause

stdioMode models the declared stdio property for every method, but only spawn forwards it. fork already has a method-specific branch (unsetStdioMode, which reads silent); the exec family has none.

Additional context

I had a four-line change ready that returns the effective mode for exec and execFile and leaves spawn/fork untouched. It makes the end-to-end output default/default/default for the three calls above and keeps the domain and acceptance suites otherwise green, but it needs expected.set("cjs-var-argv", ...) and the other exec-family rows in javascriptChildProcessStdioCli.test.ts to change, which is why I have not opened a PR for it.

Originally created by @XiaoBinGan on GitHub (Oct 26, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7217 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `analyze_javascript_application` reports a `stdio_mode` for `exec` and `execFile` from the caller's `stdio` option, but those two functions never pass `stdio` to `spawn`, so the value cannot take effect. Declared vs reported, measured through the JavaScript semantic analysis: | call | reported `stdio_mode` | child's actual stdout/stderr | | --- | --- | --- | | `exec("x")` | `default` | pipe | | `exec("x", {stdio:"ignore"})` | `ignore` | pipe | | `exec("x", {stdio:"inherit"})` | `inherit` | pipe | | `exec("x", {stdio:"pipe"})` | `pipe` | pipe | | `exec("x", {stdio:["ignore","pipe","pipe"]})` | `array` | pipe | | `exec("x", {stdio:input})` | `dynamic` | pipe | | `execFile("x", {stdio:"ignore"})` | `ignore` | pipe | | `execFile("x", [], {stdio:"inherit"})` | `inherit` | pipe | `spawn` and `fork` are unaffected: `spawn("x", {stdio:"ignore"})` reports `ignore` and the child's streams really are null there. ### Reproduction Four independent checks on Node 24.22.1, Windows x64, against `main` at `9341b116e`. 1. **Node's own source**: `require("node:child_process").execFile.toString()` builds its child from `spawn(file, args, {cwd, env, gid, shell, signal, uid, windowsHide, windowsVerbatimArguments})`. The object passed to `spawn` has no `stdio` key, and `exec` only normalizes its arguments and then calls `execFile`. 2. **The published types**: `node_modules/@types/node/child_process.d.ts` declares `interface ExecFileOptions extends CommonOptions, Abortable`, and `CommonOptions` has only `windowsHide` and `timeout`. `stdio` appears on `CommonSpawnOptions` / `SpawnOptionsWithoutStdio`, not on the exec family. 3. **Runtime probe**: `exec("echo hi", {stdio:"ignore"})` still delivers `hi` to its callback and `child.stdio` holds sockets, while `spawn(process.execPath, ["-e",""], {stdio:"ignore"})` yields `[null, null, null]`. 4. **REA end-to-end**: analyzing a directory whose `entry.mjs` calls `exec("echo ignored", {stdio:"ignore"})`, `exec("echo inherited", {stdio:"inherit"})` and `execFile("tool", {stdio:"ignore"})` emits `"stdio_mode":"ignore"`, `"stdio_mode":"inherit"` and `"stdio_mode":"ignore"`. ### Expected behavior Either reported mode is what the child actually gets (`pipe` for the exec family), or the declared-but-ineffective option is reported as an explicit limitation rather than as the effective mode. I am not asking for one over the other. The reason this is a question rather than a patch: `de9371b85` (*fix(javascript): preserve effective child-process stdio options*, #4336) introduced `tests/acceptance/applications/javascriptChildProcessStdioCli.test.ts`, and that test currently asserts the declared value for this family — `expected.set("cjs-var-argv", ["inherit", null])` for `execFile("cjs-var-argv", stable, {stdio:"inherit"})`. So the committed assertion and the commit's own "effective stdio" wording disagree, and which one is authoritative is a maintainer call. ### Impact and scope An analyst reading the child-process graph is told an `exec` call's output is discarded or inherited when Node actually pipes it and delivers it to the callback. The graph's `connects-stdio` relation is also marked `resolved` for these values, so the misread is not flagged as uncertain. ### REA version 6.8.0 source checkout at `9341b116e` ### Runtime and platform Windows x64, Node 24.22.1 ### Target and artifact context Inline JavaScript sources; no real application and no execution of the analyzed code. ### Interface CLI (`rea analyze-javascript-application`), and the same `JavaScriptSemanticIR` reaches MCP. ### Earliest failure stage Evidence, artifacts, or reconstruction ### Relevant output or evidence `src/domain/javascript/javascriptSemanticChildProcesses.ts`, `stdioMode()`: the `lookup.status === "explicit"` branch returns the declared string/array before any method-specific rule, and only `fork` then falls back to `unsetStdioMode`. Nothing consults the exec family's fixed spawn option list. ### Typed outcome or recovery expected No new error. Either the mode changes to the effective one, or the ineffective declaration surfaces as an explicit limitation. ### Analysis or suspected cause `stdioMode` models the declared `stdio` property for every method, but only `spawn` forwards it. `fork` already has a method-specific branch (`unsetStdioMode`, which reads `silent`); the exec family has none. ### Additional context I had a four-line change ready that returns the effective mode for `exec` and `execFile` and leaves `spawn`/`fork` untouched. It makes the end-to-end output `default`/`default`/`default` for the three calls above and keeps the domain and acceptance suites otherwise green, but it needs `expected.set("cjs-var-argv", ...)` and the other exec-family rows in `javascriptChildProcessStdioCli.test.ts` to change, which is why I have not opened a PR for it.
Author
Owner

@hichipli commented on GitHub (Oct 26, 2026):

Heads up: #7087 is already open for this exec/execFile case. It applies the method-specific default before reading stdio and updates the CLI/MCP acceptance coverage, including ignored dynamic options. It still needs review, but looks worth coordinating there before opening another implementation.

<!-- gh-comment-id:6412677829 --> @hichipli commented on GitHub (Oct 26, 2026): Heads up: #7087 is already open for this exec/execFile case. It applies the method-specific default before reading `stdio` and updates the CLI/MCP acceptance coverage, including ignored dynamic options. It still needs review, but looks worth coordinating there before opening another implementation.
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#4303
No description provided.