Skip to content

[vite 8.2.0] Fix REFLEX_ENV_MODE leak between AppHarness instances - #6857

Open
masenf wants to merge 6 commits into
mainfrom
claude/vite-8-2-0-test-memo-regression-h6shlg
Open

[vite 8.2.0] Fix REFLEX_ENV_MODE leak between AppHarness instances#6857
masenf wants to merge 6 commits into
mainfrom
claude/vite-8-2-0-test-memo-regression-h6shlg

Conversation

@masenf

@masenf masenf commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Description

Fixes a critical bug where AppHarnessProd leaked REFLEX_ENV_MODE=prod into subsequent dev AppHarness instances in the same process. When AppHarnessProd runs export(), it sets REFLEX_ENV_MODE=prod process-wide and never restores it. A dev AppHarness compiling later would then write prerender: true into 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_MODE leak, not by vite itself.

Changes

  1. reflex/testing.py: Pin REFLEX_ENV_MODE to dev in AppHarness._initialize_app(), mirroring what reflex run does. This ensures dev harnesses always compile in dev mode regardless of prior AppHarnessProd instances.

  2. 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.

  3. Tests: Added test_app_harness_initialize_resets_leaked_prod_env_mode() to verify that dev AppHarness correctly resets REFLEX_ENV_MODE to dev mode and disables route prerendering, even when the environment variable was previously set to prod.

  4. 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-test failures that upgrade introduced on main:

  1. packages/reflex-base/src/reflex_base/compiler/templates.py: Pin preview.host to 127.0.0.1 in the generated vite config. react-router 8 prerenders by fetching pages from a vite preview server started with the project's config; with no pinned host, the bound socket and the fetched http://localhost:<port>/ URL can resolve to different loopback address families (as in docker containers), failing reflex export with Prerender: 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.

  2. reflex/utils/exec.py: Pre-enable the development export 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

  • New regression test test_app_harness_initialize_resets_leaked_prod_env_mode() verifies the env-mode fix
  • New unit tests assert the generated vite config pins the preview host, and that the development condition is merged into NODE_OPTIONS/BUN_OPTIONS
  • Existing unit tests pass; memo playwright suites pass 10/10 on the merged react-router 8.3.0 + vite 8.2.0 tree
  • Node-free verification (reflex-managed bun 1.3.14, mirroring the docker image): fresh blank app reflex export succeeds, and reflex run --env dev serves 200 with zero restart errors

https://claude.ai/code/session_01AzeRM83L6J367wmwk9Gezk

&lt;img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"&gt;

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
@masenf
masenf requested a review from a team as a code owner August 7, 2026 18:16
@greptile-apps

greptile-apps Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The 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.

  • Resets REFLEX_ENV_MODE during development harness initialization.
  • Upgrades Vite from 8.0.16 to 8.2.0.
  • Configures the generated Vite preview host as 127.0.0.1.
  • Merges the development condition into Node and Bun runtime options.
  • Adds regression coverage and changelog fragments.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

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

@codspeed-hq

codspeed-hq Bot commented Aug 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/vite-8-2-0-test-memo-regression-h6shlg (327cecd) with main (4363732)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 5 files

Re-trigger cubic

claude added 2 commits August 7, 2026 20:48
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
@masenf

masenf commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

The reflex-install-and-init failures on this PR are not caused by this diff — and the root cause is now fixed here.

What broke: reflex-init-in-docker-test fails on every run whose checkout contains the react-router 8.3.0 upgrade (4116ff2 / #6854), starting with its own push run on main at 16:51Z — identical Prerender: Request failed for /: ECONNREFUSED at vite.js:887. PR runs check out the merge with current main, so this PR inherited it on both attempts, as did claude/auth-overview-redirect-rsj733 (19:53Z, 20:06Z). Re-runs of workflow runs created before the upgrade merged still pass on their pre-upgrade merge commits (e.g. fix/splat-catchall at 18:08Z) — that's why the failure looked flaky. Note #6854's head ran no Actions workflows at all (only cubic + Greptile), so this path was first exercised post-merge.

Mechanism: react-router 8 prerenders through the Vite Environment API path: startPreviewServer boots a vite preview server with the project's vite config (inline options set only port: 0) and fetches each path from resolvedUrls.local[0], i.e. http://localhost:<port>/. In the docker container localhost maps to both 127.0.0.1 and ::1, and the bound socket vs. the prerender fetch can resolve to different address families under bun → connection refused. Dev machines with IPv4-only localhost never hit it, which is why #6854's local verification (and my sandbox) passed. react-router 7 was immune because its build doesn't use the HTTP prerender path (the passing logs have no "Using Vite Environment API"). TanStack Router hit the identical bug in their preview-server prerender — builds fine locally, ECONNREFUSED in docker — and fixed it by pinning the preview host: TanStack/router#6275TanStack/router#6305.

Fix (d5cc49a): pin preview: { host: "127.0.0.1" } in the generated vite config. react-router's inline preview options don't set host, so the config-file value survives the merge; the server binds the literal IPv4 loopback and resolvedUrls.local[0] becomes http://127.0.0.1:<port>/ — no hostname resolution left in the loop. Unit test added asserting the pin.

Verification on this branch merged with main (react-router 8.3.0 + vite 8.2.0): fresh blank app reflex export with node and bun stripped from PATH (reflex downloads its pinned bun 1.3.14, mirroring the docker image) succeeds — the preview server binds 127.0.0.1 and serves 200; the memo playwright suites pass 10/10 on the merged combination. My sandbox has no docker daemon and no IPv6 stack, so the in-container repro is validated by this PR's own CI run — watching it now.

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
@masenf

masenf commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Second react-router 8.3.0 fallout found and fixed (84adcbf). The reflex-install-and-init run on d5cc49a got past the prerender failure — confirming the preview.host fix in the actual docker environment — and then crashed in the dev phase: react-router 8's dev CLI requires the development export condition (probed via its #development-condition-enabled import map) and re-executes itself with NODE_OPTIONS=--conditions=development to enable it. bun does not apply that option when it executes the CLI — and with no node in the container, bun is what runs it — so the restarted process probes false again and trips the CLI's restart guard: restartWithMergedOptions() was called, but the process has already been restarted. The dev server exited, and the workflow hung waiting on the frontend port until cancelled (that's the wedged 55-minute run — I cancelled it after it blew past the 30-minute job timeout). Environments with node installed never see this because the CLI's #!/usr/bin/env node shebang wins, which is why the 24-job integration matrix stayed green while the docker test died.

Fix: run_frontend now appends --conditions=development to NODE_OPTIONS and BUN_OPTIONS (merged with any existing value, idempotent) before spawning the dev server. Under bun, the CLI's restarted process picks BUN_OPTIONS back up from the environment and comes up with the condition enabled; under node, the condition is active up front and react-router's self-restart is skipped entirely. Prod is untouched — run_frontend_prod serves compiled static files with no react-router CLI, and vite's bundle resolution conditions are mode-driven.

Verified node-free (reflex-managed bun 1.3.14, mirroring the docker image): plain bun run dev reproduces the CI crash exactly; with the fix, reflex run --env dev on a fresh blank app serves 200 with zero restart errors. Unit tests added.

Heads-up for main: after cherry-picking the prerender fix, main will also need this one — the docker workflow runs reflex run --env dev right after the export. Both fixes are self-contained commits (d5cc49a, 84adcbf).


Generated by Claude Code

@cubic-dev-ai cubic-dev-ai 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.

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

Comment thread reflex/utils/exec.py Outdated
claude added 2 commits August 7, 2026 21:28
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
@masenf masenf changed the title Fix REFLEX_ENV_MODE leak between AppHarness instances [vite 8.2.0] Fix REFLEX_ENV_MODE leak between AppHarness instances Aug 8, 2026
@masenf
masenf enabled auto-merge (squash) August 8, 2026 00:02
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