Skip to content

fix: eval#383

Merged
lazarv merged 1 commit intomainfrom
fix/eval
Apr 9, 2026
Merged

fix: eval#383
lazarv merged 1 commit intomainfrom
fix/eval

Conversation

@lazarv
Copy link
Copy Markdown
Owner

@lazarv lazarv commented Apr 9, 2026

Summary

Previously, react-server and react-server build would silently treat piped stdin as the server entrypoint whenever fd 0 happened to be a pipe, file redirect, or (in production) any non-TTY context outside CI. That made the CLI's behavior
depend on invisible environmental state: the same command could behave differently depending on whether it was invoked from a shell, an editor task runner, a process manager, or a subprocess whose parent happened to close stdin. It also meant
stdin could be read without the user ever asking for it, which is surprising at best and unsafe at worst.

This change makes --eval the single, explicit opt-in. Stdin is now only consulted when the user passes --eval, and nothing else changes the CLI's entrypoint resolution.

Behavior

--eval "<code>" passes the inline string as the virtualized server entrypoint, the same as before. --eval with no value reads the full entrypoint from stdin — this is the new, explicit way to pipe code in. With no --eval at all, stdin is
never touched, even if it is a pipe or a file redirect; the CLI falls back to the positional root or the file-router as it always did.

The JS-level signal is that the plugin's options.eval is now tri-valued: string for inline code, true for "read stdin", and anything else (undefined / false) for "don't use eval at all". The CLI option definition changed from -e, --eval <code> (required value) to -e, --eval [code] (optional value) in both dev and build commands to support the bare form.

Why

The previous auto-detection had three concrete problems. It could consume stdin from an unrelated parent process that happened to leave fd 0 open as a pipe, producing confusing "Root module not provided" errors or worse, running
attacker-controlled code. It was non-deterministic across environments: the production path additionally gated on !process.env.CI, so the same invocation would take different branches in CI vs. local. And it made the --eval flag's contract
ambiguous, because stdin could be the entrypoint even when --eval was absent, so users couldn't reason about the CLI from the flags alone.

Making stdin opt-in via --eval collapses these three cases into one rule that is trivial to explain and impossible to trigger by accident.

Changes

lib/plugins/react-server-eval.mjs drops the fstatSync-based isStdinPiped probe entirely. The load handler now branches cleanly on options.eval: string → return it verbatim, true → read stdin, otherwise → return the "Root module not
provided" stub. lib/dev/action.mjs no longer runs its own fstat on fd 0 to pick the virtual entrypoint; it selects the virtual module only when options.eval != null && options.eval !== false. lib/build/server.mjs drops the
!process.stdin.isTTY && !process.env.CI heuristic at the production root-module decision and uses the same explicit check. bin/commands/dev.mjs and bin/commands/build.mjs change the cac option shape to -e, --eval [code] with clarified
help text describing both forms.

Docs

Both the English and Japanese features/cli.mdx have been updated for the dev and build --eval sections. The prose explicitly calls out that stdin is never auto-consumed, and each section now carries three runnable examples: inline code,
bare --eval with a pipe, and bare --eval with a shell file redirect.

Tests

Two new specs guard the contract at complementary levels. test/__test__/react-server-eval.spec.mjs is a fast unit spec that imports the react-server:eval plugin directly and exercises its load hook with a poisoned process.stdin that
throws on read. It verifies that the "no --eval", "eval: false", and "--eval "<inline>"" paths all return the expected result without ever touching stdin, and that eval: true correctly reads both single-chunk and multi-chunk stdin
payloads. The poisoned-stdin trick is what lets the test make a positive assertion about the absence of stdin reads, which a black-box HTTP test cannot observe.

test/__test__/cli-eval.spec.mjs is an end-to-end spec that spawns the real CLI binary as a subprocess. In dev mode it covers three cases: --eval "<inline>" serves the inline marker, bare --eval + piped stdin serves the stdin marker, and
a positional root file + piped invalid JavaScript serves the positional marker (the critical regression guard — if auto-eval ever comes back, the bogus stdin would either crash the server or be rendered instead of the positional root). In
production mode it runs build --eval "<inline>" with bogus stdin piped in and asserts the build exits successfully, covering the build/server.mjs code path. The subprocess helper forces NO_COLOR=1 and strips ANSI defensively so readiness
detection isn't broken by colored log output.

Compatibility

This is a behavior change that could in principle affect users who were relying on the undocumented auto-stdin path. In practice, the documented way to pipe code was always --eval, and the auto path was an implementation detail of the
plugin. Users who were piping code without --eval need to add the flag. No API surface outside the CLI is affected.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
react-server-docs ce8632d Apr 09 2026, 12:43 PM

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 9, 2026

❌ 14 Tests Failed:

Tests completed Failed Passed Skipped
922 14 908 3
View the top 3 failed test(s) by shortest run time
__test__/scroll-restoration.spec.mjs > scroll restoration: multiple back/forward preserves positions
Stack Traces | 27.5s run time
AssertionError: expected 0 to be greater than 400
 ❯ __test__/scroll-restoration.spec.mjs:158:25
__test__/apps/react-query.spec.mjs > react-query load
Stack Traces | 157s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/react-query.spec.mjs:15:3
__test__/apps/mantine.spec.mjs > mantine > dates > locale select changes date format
Stack Traces | 225s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:129:7
__test__/apps/mantine.spec.mjs > mantine > form > shows validation error on empty submit
Stack Traces | 225s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:99:7
__test__/apps/mantine.spec.mjs > mantine > notification system > shows notification on button click
Stack Traces | 257s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:166:7
__test__/apps/mantine.spec.mjs > mantine > home page > increment button updates count
Stack Traces | 257s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:83:7
__test__/apps/mantine.spec.mjs > mantine > dates > date input formats value
Stack Traces | 257s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:115:7
__test__/apps/mantine.spec.mjs > mantine > setup > start server
Stack Traces | 284s run time
Error: waitForHydration timed out after 60000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:63:9
__test__/apps/mantine.spec.mjs > mantine > navigation progress > shows progress bar on start
Stack Traces | 289s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:251:7
__test__/apps/mantine.spec.mjs > mantine > home page > renders home page with Mantine UI
Stack Traces | 289s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:75:7
__test__/apps/mantine.spec.mjs > mantine > carousel > navigates carousel slides
Stack Traces | 289s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:222:7
__test__/apps/mantine.spec.mjs > mantine > spotlight > opens spotlight and searches for items
Stack Traces | 289s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:184:7
__test__/apps/mantine.spec.mjs > mantine > modals manager > opens and confirms modal
Stack Traces | 321s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:274:7
__test__/apps/mantine.spec.mjs > mantine > rich text editor > renders rich text editor content
Stack Traces | 321s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:313:7
__test__/apps/mantine.spec.mjs > mantine > charts > renders chart SVGs
Stack Traces | 321s run time
Error: waitForHydration timed out after 30000ms
 ❯ waitForHydration utils.mjs:117:13
 ❯ __test__/apps/mantine.spec.mjs:153:7

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

⚡ Benchmark Results

PR ce8632d main 6b845f6
Config 50 connections, 10s/test 50 connections, 10s/test
Benchmark Req/s vs main Avg Latency vs main P99 Latency Throughput
minimal 1176 🔴 -13.8% 41.93 ms 🔴 +16.1% 92 ms 0.7 MB/s
small 1321 🔴 -6.1% 37.27 ms 🔴 +6.5% 67 ms 1.2 MB/s
medium 324 🔴 -5.9% 152.83 ms 🔴 +6.2% 219 ms 4.7 MB/s
large 29 🔴 -12.2% 1553.83 ms 🔴 +7.1% 3013 ms 2.9 MB/s
deep 850 🔴 -7.6% 58.18 ms 🔴 +8.4% 102 ms 2.8 MB/s
wide 40 🔴 -1.5% 1230.63 ms 🔴 +7.5% 1860 ms 2.2 MB/s
cached 3362 🔴 -6.2% 14.41 ms 🔴 +7.9% 31 ms 49.0 MB/s
client-min 480 🟢 +4.3% 103.22 ms 🟢 -4.2% 171 ms 2.4 MB/s
client-small 500 🟢 +4.7% 99.27 ms 🟢 -4.3% 149 ms 2.7 MB/s
client-med 376 🟢 +4.3% 131.55 ms 🟢 -4.3% 185 ms 7.2 MB/s
client-large 83 🔴 -2.2% 567.05 ms ⚪ +1.0% 1023 ms 8.8 MB/s
client-deep 466 🟢 +6.0% 106.23 ms 🟢 -5.6% 160 ms 3.6 MB/s
client-wide 136 🔴 -9.4% 354.71 ms 🔴 +8.7% 632 ms 8.0 MB/s
static-json 8618 🟢 +17.4% 5.19 ms 🟢 -17.5% 17 ms 3.6 MB/s
static-js 8626 🟢 +16.6% 5.17 ms 🟢 -16.5% 16 ms 10.3 MB/s
404-miss 5102 ⚪ +0.6% 9.37 ms 🟢 -1.4% 21 ms 0.6 MB/s
hybrid-min 486 🟢 +7.5% 101.53 ms 🟢 -7.3% 196 ms 2.7 MB/s
hybrid-small 472 🟢 +8.0% 105.15 ms 🟢 -7.3% 166 ms 3.1 MB/s
hybrid-medium 193 🟢 +1.2% 253.32 ms 🟢 -1.4% 411 ms 8.7 MB/s
hybrid-large 23 🔴 -6.5% 1896.43 ms 🔴 +1.6% 2854 ms 7.8 MB/s
hybrid-deep 350 🟢 +1.4% 140.49 ms 🟢 -2.2% 206 ms 5.2 MB/s
hybrid-wide 31 🔴 -2.5% 1458.89 ms 🔴 +2.8% 2428 ms 6.7 MB/s
hybrid-cached 2742 🔴 -5.8% 17.73 ms 🔴 +6.3% 35 ms 122.8 MB/s
hybrid-client-min 521 🟢 +7.5% 95.26 ms 🟢 -6.7% 146 ms 2.7 MB/s
hybrid-client-small 512 🟢 +4.4% 96.42 ms 🟢 -4.3% 143 ms 2.8 MB/s
hybrid-client-medium 375 ⚪ -0.2% 131.61 ms ⚪ -0.4% 190 ms 7.2 MB/s
hybrid-client-large 85 🟢 +1.3% 574.79 ms 🔴 +1.3% 1122 ms 9.0 MB/s
hybrid-client-deep 458 🟢 +2.4% 108.05 ms 🟢 -2.6% 202 ms 3.6 MB/s
hybrid-client-wide 140 ⚪ -0.7% 352.76 ms 🔴 +1.4% 584 ms 8.2 MB/s
Legend

🟢 > 1% improvement | 🔴 > 1% regression | ⚪ within noise margin

Benchmarks run on GitHub Actions runners (shared infrastructure) — expect ~5% variance between runs. Consistent directional changes across multiple routes are more meaningful than any single number.

@lazarv lazarv merged commit 70a320f into main Apr 9, 2026
143 of 146 checks passed
@lazarv lazarv deleted the fix/eval branch April 9, 2026 13:11
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.

2 participants