Skip to content

cli: read the script from stdin for node - in node emulation - #38566

Open
robobun wants to merge 1 commit into
mainfrom
farm/2ea09b16/as-node-stdin-dash
Open

robobun wants to merge 1 commit into
mainfrom
farm/2ea09b16/as-node-stdin-dash

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • When bun runs as node (argv0 is node: the shim bun --bun puts on PATH, or a node symlink to bun), node - does not read the script from stdin. It fails with error: Module not found '<cwd>/-' and exit code 1; if a file named - happens to exist in cwd, it runs that file instead. some-tool | node - is the standard node way to run a generated script, and it works in plain bun (bun run -).
  • Cause: RunCommand::exec_as_if_node (src/runtime/cli/run_command.rs) goes from the -e/-p branch straight to "the positional is a file name" and joins it onto cwd. It never checks for the - marker. The bun run - path (exec_with_cfg -> exec_stdin) does. Not a regression: the Zig execAsIfNode had the same shape.

Fix

  • exec_as_if_node now routes a bare - positional to the existing exec_stdin, the same function bun run - uses. A failed stdin read exits 1, which is also what bun run - does.
  • Why this is right: it matches node. echo 'console.log(process.argv.slice(1))' | node - a b prints ["-","a","b"] (node v26.3.0). Bun's argument parser stops at the first positional for the node command, so a b are already in ctx.passthrough, and exec_stdin prepends the - itself, so the argv comes out the same. -e/-p are checked earlier in the function and still win over a - positional, as in node, and node ./- still runs a file of that name because only the bare - is the marker.
  • Not changed: process.execArgv for a stdin script still contains "-". That is computed elsewhere (create_exec_argv in node_process.rs) and is the same for bun run - today; it is a separate fix.
  • Verified: test/cli/run/as-node.test.ts, new node - runs the script from stdin block (argv for -, - a b, - --flag -x, -- - a; exit code propagation; --require preload before the stdin script). All 6 fail on the released binary and pass with this change. The rest of as-node.test.ts and test/cli/run/run-eval.test.ts (the bun run - coverage) still pass with the debug build.

Background

  • Node emulation: when bun's argv0 is node, Command::which (src/runtime/cli/mod.rs) dispatches to RunAsNodeCommand, whose body is exec_as_if_node. It imitates the node CLI rather than bun run: no package.json script lookup, no node_modules/.bin lookup, the positional is always a file.
  • exec_stdin: reads all of stdin into ctx.runtime_options.eval.script and boots the VM with the synthetic entry point <cwd>/[stdin]; the module loader serves that path from the in-memory script (the same mechanism [eval] uses). It also pushes "-" to the front of ctx.passthrough, which becomes process.argv after the executable.
  • positionals vs passthrough: Arguments::parse uses stop_after_positional_at = 1 for the node command, so ctx.positionals holds only the script argument and every later argument lands in ctx.passthrough unparsed. That is why the new branch only has to look at positionals[0].

RunCommand::exec_as_if_node treated every positional as a file name, so
`node -` (argv0 = node, or the node shim bun installs) tried to load a
file literally named "-" and failed with "Module not found". Route the
bare "-" positional through exec_stdin, the same path `bun run -` uses,
so the script is read from stdin and process.argv[1] is "-" like node.
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next review available in: 34 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 98463d18-953b-4a67-877c-917644102d00

📥 Commits

Reviewing files that changed from the base of the PR and between eabb96d and de1a1d9.

📒 Files selected for processing (2)
  • src/runtime/cli/run_command.rs
  • test/cli/run/as-node.test.ts

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

@robobun

robobun commented Aug 14, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: fix pushed, waiting on CI. Automated review so far has no findings.

Reproduced on bun 1.4.0-canary.1+b7a043103 and a debug build of main (eabb96d), linux-x64:

$ echo 'console.log(JSON.stringify(process.argv.slice(1)))' | bun --bun node - a b
error: Module not found '/tmp/dash-probe/-'
$ echo 'console.log(JSON.stringify(process.argv.slice(1)))' | node - a b      # node v26.3.0
["-","a","b"]

With this branch the first command prints ["-","a","b"] too. The new tests in test/cli/run/as-node.test.ts fail on the released binary and pass with the debug build; test/cli/run/run-eval.test.ts (bun run -) still passes.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — small, well-scoped Node compat fix that routes node - through the existing exec_stdin path.

What was reviewed:

  • Placement of the new branch in exec_as_if_node: after the -e/-p and empty-positionals checks, before the filename join — so -e still wins and positionals[0] is safe to index.
  • Ok(false) → Global::exit(1) matches how the bun run - caller handles the same return (mod.rs:1474-1475).
  • Only bare - matches (./- still falls through to the filename path); tests plant a literal - file in cwd to prove the old behavior is gone.
  • Test coverage: argv shape across 4 variants, exit-code propagation, and --require ordering; follows existing harness conventions in the same file.
Extended reasoning...

Overview

Two-file change: a 10-line addition in src/runtime/cli/run_command.rs inserting a positionals[0] == b"-" check into exec_as_if_node that dispatches to the pre-existing exec_stdin helper (already used by bun run -), plus a doc-comment update on exec_stdin. Tests add a describe block with 6 cases to test/cli/run/as-node.test.ts.

Security risks

None. This is CLI argument dispatch — no untrusted parsing, no memory management, no unsafe blocks. The only new comparison is a byte-slice equality against a literal.

Level of scrutiny

Low-medium. The change is a targeted Node.js CLI compat fix that reuses existing, already-tested machinery (exec_stdin) rather than introducing new logic. The Global::exit(1) on Ok(false) mirrors the existing bun run - caller in mod.rs. The branch is placed correctly relative to the -e/-p and empty-positionals guards, and only an exact - triggers it (so node ./- still runs a file). No CODEOWNERS on this path.

Other factors

Test quality is good: uses test.each for the argv matrix, asserts stdout before exitCode per repo convention, plants a decoy file named - in cwd so the old "run the file" behavior would loudly fail, and covers --require interaction and exit-code propagation. The PR description explicitly verified all 6 tests fail on the released binary and pass with the change, and that the surrounding as-node.test.ts and run-eval.test.ts still pass. The known process.execArgv divergence is called out as pre-existing and out of scope.

steipete added a commit to steipete/bun that referenced this pull request Sep 18, 2026
Integrate the independently authored and reviewed 0aba87964e2f CLI fix
into the custom fork. Preserve the existing eval-argv owner and bare
input-type stdin routing, including explicit-dash argv markers.

Version-query upstream publication is separate; bare and empty-source
followup will credit the existing oven-sh#38566 explicit-dash work.
steipete added a commit to openclaw/bun that referenced this pull request Sep 30, 2026
Integrate the independently authored and reviewed 0aba87964e2f CLI fix
into the custom fork. Preserve the existing eval-argv owner and bare
input-type stdin routing, including explicit-dash argv markers.

Version-query upstream publication is separate; bare and empty-source
followup will credit the existing oven-sh#38566 explicit-dash work.

(cherry picked from commit 2e3ce72)
steipete added a commit to openclaw/bun that referenced this pull request Sep 30, 2026
Integrate the independently authored and reviewed 0aba87964e2f CLI fix
into the custom fork. Preserve the existing eval-argv owner and bare
input-type stdin routing, including explicit-dash argv markers.

Version-query upstream publication is separate; bare and empty-source
followup will credit the existing oven-sh#38566 explicit-dash work.

(cherry picked from commit 2e3ce72)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant