[vite 8.2.0] Fix REFLEX_ENV_MODE leak between AppHarness instances - #6857
[vite 8.2.0] Fix REFLEX_ENV_MODE leak between AppHarness instances#6857masenf wants to merge 6 commits into
Conversation
The full-suite test_memo/test_memoize_edge_cases failures that held the vite pin at 8.0.16 were not a vite defect. AppHarnessProd runs export(), which sets REFLEX_ENV_MODE=prod for the whole pytest process and never restores it. Every dev AppHarness compiled afterwards saw should_prerender_routes() == True and wrote "prerender": true into its dev react-router.config.js, so `react-router dev` served fully prerendered page HTML instead of the SPA fallback shell. Pages whose prerendered HTML is hydration-hostile (the memoize edge-cases app renders nested <button>s, which the HTML parser restructures) then fail React hydration on every load, and an event dispatched during the hydration-failure recovery window is dropped before it reaches the websocket. vite 8.2.0's dev-server timing makes the tests' first interaction land inside that window nearly every time, while 8.0.16's timing let recovery win the race - which is why the regression bisected to the vite bump even though the leak is the cause. Pin REFLEX_ENV_MODE=dev in AppHarness._initialize_app - mirroring what `reflex run` does - so a leaked prod mode from a prior harness cannot reach the next app's compile, and bump vite to 8.2.0. Verified locally: full tests/integration/tests_playwright suite green on vite 8.2.0 with the fix (247 passed, 0 reruns); without the fix the same run yields 4 failed + 12 rerun with the exact signature of the CI failures on the bisection branch (run 30835534751). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzeRM83L6J367wmwk9Gezk
Greptile SummaryThe PR prevents production environment mode from leaking into later development harnesses and updates the bundled Vite version. It also pins the prerender preview server to IPv4 loopback and pre-enables React Router’s development export condition for spawned dev servers.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| reflex/testing.py | Resets environment mode before harness compilation, while production harnesses still switch explicitly to production during export. |
| reflex/utils/exec.py | Adds an environment-copy helper that preserves existing runtime options and enables the development export condition for frontend dev processes. |
| packages/reflex-base/src/reflex_base/compiler/templates.py | Pins the generated Vite preview server to IPv4 loopback for React Router prerendering. |
| packages/reflex-base/src/reflex_base/constants/installer.py | Updates the generated project’s Vite development dependency to 8.2.0. |
| tests/units/test_testing.py | Adds regression coverage confirming a development harness clears leaked production mode and disables prerendering. |
| tests/units/utils/test_exec.py | Covers development-condition insertion, option preservation, deduplication, and non-mutation of the source environment. |
| tests/units/utils/test_utils.py | Verifies that generated Vite configuration pins the preview host. |
Reviews (5): Last reviewed commit: "Rename news fragments to PR 6857 and ded..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
…t-memo-regression-h6shlg
react-router 8.x prerenders by starting a `vite preview` server with the project's vite config and fetching each path from `resolvedUrls.local[0]`, which is `http://localhost:<port>/` when no preview host is configured. Where `localhost` maps to both loopback families (docker containers among others), the bound socket and the fetched URL can resolve to different address families and the export dies with `Prerender: Request failed for /: ECONNREFUSED`. This is why reflex-init-in-docker-test has failed on every run containing the react-router 8.3.0 upgrade (4116ff2), starting with its own merge to main, while dev machines with IPv4-only `localhost` build fine. Pin `preview.host` to 127.0.0.1 in the generated vite config so the preview server binds the same literal address the prerender fetch dials; react-router's inline preview options set only port/open, so the host from the config file survives the merge. TanStack Router shipped the same fix for the identical failure (TanStack/router#6275, #6305). Verified: with node and bun removed from PATH (mirroring the docker image; reflex downloads its pinned bun 1.3.14), `reflex export` of a fresh blank app on this tree succeeds, binding 127.0.0.1 with resolved URL http://127.0.0.1:<port>/. The memo playwright suites pass 10/10 on the merged react-router 8.3.0 + vite 8.2.0 combination. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzeRM83L6J367wmwk9Gezk
|
The What broke: Mechanism: react-router 8 prerenders through the Vite Environment API path: Fix (d5cc49a): pin Verification on this branch merged with main (react-router 8.3.0 + vite 8.2.0): fresh Also merged main into this branch (417889f) so CI exercises the real shipping combination. If you'd rather land the prerender fix on main independently of this PR, d5cc49a cherry-picks cleanly. Generated by Claude Code |
react-router 8's dev CLI imports "#development-condition-enabled", a resolution probe that is true only when the runtime has the `development` export condition active. When it is not, the CLI re-executes itself with NODE_OPTIONS=--conditions=development; bun does not apply that option when it executes the CLI on installs without node, so the restarted process probes false again and trips the CLI's restart guard: "restartWithMergedOptions() was called, but the process has already been restarted", killing the dev server. This is the second react-router 8.3.0 fallout in reflex-init-in-docker-test - the run on this branch got past the (fixed) prerender failure, crashed here in the dev phase, and hung the workflow waiting on a frontend that had exited. Environments with node installed never see it because the CLI's node shebang wins, which is why the integration matrix stayed green. Pre-enable the condition in run_frontend for both runtimes by appending --conditions=development to NODE_OPTIONS and BUN_OPTIONS (merged with any existing value, idempotent). Under bun the CLI's restarted process picks BUN_OPTIONS up from the environment and comes up with the condition enabled; under node the condition is active up front and the restart is skipped entirely. Prod is unaffected: run_frontend_prod serves compiled static files without the react-router CLI, and vite's bundle resolution conditions are mode-driven, not process-driven. Verified in a node-free environment (reflex-managed bun 1.3.14, mirroring the docker image): `reflex run --env dev` on a fresh blank app now serves 200 with zero restart errors, where it previously reproduced the CI crash exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzeRM83L6J367wmwk9Gezk
|
Second react-router 8.3.0 fallout found and fixed (84adcbf). The Fix: Verified node-free (reflex-managed bun 1.3.14, mirroring the docker image): plain Heads-up for main: after cherry-picking the prerender fix, main will also need this one — the docker workflow runs Generated by Claude Code |
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Apply --conditions=development to the spawned dev server's environment in run_process_and_launch_url instead of mutating os.environ in run_frontend, so the condition cannot leak into unrelated node/bun subprocesses spawned later in the same process - the same leak shape this PR fixes for REFLEX_ENV_MODE. The helper now returns a merged copy and the unit tests assert the parent environment is left untouched. Re-verified node-free (reflex-managed bun 1.3.14): `reflex run --env dev` on a fresh blank app serves 200 with zero restart errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzeRM83L6J367wmwk9Gezk
Each change now has one fragment in the package that owns the code: the AppHarness env-mode leak and dev-server development condition fixes (reflex/) keep top-level fragments (6857.bugfix.md and 6857.bugfix.1.md), while the prerender preview host pin and the vite 8.2.0 bump (reflex-base) keep only reflex-base fragments (6857.bugfix.md and 6857.misc.md). Cross-package duplicates removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzeRM83L6J367wmwk9Gezk
Type of change
Description
Fixes a critical bug where
AppHarnessProdleakedREFLEX_ENV_MODE=prodinto subsequent devAppHarnessinstances in the same process. WhenAppHarnessProdrunsexport(), it setsREFLEX_ENV_MODE=prodprocess-wide and never restores it. A devAppHarnesscompiling later would then writeprerender: trueinto its dev react-router config, causing the dev server to serve prerendered page HTML. On pages with hydration hazards, this failed hydration on every load and dropped events dispatched during the hydration-recovery window.Root cause: The environment variable leak was masked by a vite 8.0.16 pin that was added to work around memo component re-rendering failures in the full integration suite. Those failures were actually caused by this
REFLEX_ENV_MODEleak, not by vite itself.Changes
reflex/testing.py: PinREFLEX_ENV_MODEtodevinAppHarness._initialize_app(), mirroring whatreflex rundoes. This ensures dev harnesses always compile in dev mode regardless of priorAppHarnessProdinstances.packages/reflex-base/src/reflex_base/constants/installer.py: Bump vite from 8.0.16 to 8.2.0 now that the root cause of the memo regression is fixed.Tests: Added
test_app_harness_initialize_resets_leaked_prod_env_mode()to verify that devAppHarnesscorrectly resetsREFLEX_ENV_MODEto dev mode and disables route prerendering, even when the environment variable was previously set to prod.News fragments: Added changelog entries documenting the bugfix and vite bump.
Update (2026-08-07): react-router 8.3.0 merge + docker-test fixes
Merged main (react-router 8.3.0 upgrade, #6854) into this branch so CI exercises the shipping combination, and fixed the two
reflex-init-in-docker-testfailures that upgrade introduced on main:packages/reflex-base/src/reflex_base/compiler/templates.py: Pinpreview.hostto127.0.0.1in the generated vite config. react-router 8 prerenders by fetching pages from avite previewserver started with the project's config; with no pinned host, the bound socket and the fetchedhttp://localhost:<port>/URL can resolve to different loopback address families (as in docker containers), failingreflex exportwithPrerender: Request failed for /: ECONNREFUSED. Same fix TanStack Router shipped for the identical failure (fix: prerendering with docker build TanStack/router#6305). Full diagnosis in this comment.reflex/utils/exec.py: Pre-enable thedevelopmentexport condition (NODE_OPTIONS/BUN_OPTIONS) when starting the dev server. react-router 8's dev CLI otherwise re-executes itself to enable the condition, which bun does not honor on installs without node, tripping the CLI's restart guard and exiting the dev server. Details in this comment.Test Plan
test_app_harness_initialize_resets_leaked_prod_env_mode()verifies the env-mode fixNODE_OPTIONS/BUN_OPTIONSblankappreflex exportsucceeds, andreflex run --env devserves 200 with zero restart errorshttps://claude.ai/code/session_01AzeRM83L6J367wmwk9Gezk
<img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg">