fix(node): preserve stdio fds for sync child processes - #36855
Tiancheng-Xu wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
It modifies Node.js compatibility process-spawning stdio handling, which warrants manual validation across platforms and stdio configurations beyond what can be proven from the diff alone.
Pull request overview
This PR fixes Deno’s Node.js child_process stdio option conversion so that when callers pass Deno’s process.stdin/stdout/stderr streams (which expose their fd via stream.fd), the child process inherits those OS file descriptors instead of falling back to JS-level piping. It also avoids calling pause() on writable stdio streams, and adds a regression test covering spawnSync() with process stdio streams.
Changes:
- Teach
streamHandleFd()to readstream.fd(in addition to_handle.fd) so Deno’s process stdio streams can be passed through as real fds. - Guard
pause()/readStop()calls so writable stdio streams don’t throw when used in stdio conversion. - Add a unit regression test asserting
spawnSync()returnsnullstdout/stderrwhen using process stdio streams (i.e., they’re inherited, not piped).
File summaries
| File | Description |
|---|---|
ext/node/polyfills/internal/child_process.ts |
Preserves fds from Deno’s process stdio streams during stdio conversion and avoids calling pause() on non-readable streams. |
tests/unit_node/child_process_test.ts |
Adds a regression test ensuring spawnSync() inherits process stdio streams (so stdout/stderr are null). |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
The completed CI run has seven specs jobs failing on the same unrelated |
Summary
pause()on writable process stdout/stderr streams.process.execPathis spawned without Node arguments, matching Node's no-argument stdin semantics.spawnSync()with process stdio streams.Fixes #36834.
User impact and root cause
Passing
process.stdoutorprocess.stderrtospawnSync()previously lost the underlying file descriptor. Preserving it exposed a separate compatibility issue: Deno translated a no-argument Node invocation todeno run -A -, which interpreted piped stdin as ESM and failed on CommonJSrequire(). The follow-up keeps the stdio fix and adds--ext=cjsonly for that no-argument stdin path.Testing
Current head:
./x test-node child_process_test(passed)spawnSync(process.execPath, [], { input, stdio: [...] })(exit status 0)./x build(passed with a temporary local Apple-linker configuration workaround)rustfmt --checkforext/node/ops/node_cli_parser.rs(passed)git diff --checkfor the follow-up change (passed)Earlier unchanged validation:
./x lint-jsdeno x --yes npm:pagefind --help(exits 0 and prints help output)The repository-wide format check was attempted, but the local environment could not start the Rust formatter process. Upstream CI for the new head is pending.
AI tools assisted with implementation and review. I verified the final changes and the validation results reported above.