Skip to content

compile: do not inline NODE_ENV into runtime-transpiled files when .env autoload is off - #38672

Open
robobun wants to merge 1 commit into
mainfrom
farm/d9f482f7/standalone-no-dotenv-env-inlining
Open

compile: do not inline NODE_ENV into runtime-transpiled files when .env autoload is off#38672
robobun wants to merge 1 commit into
mainfrom
farm/d9f482f7/standalone-no-dotenv-env-inlining

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • A bun build --compile executable built with --no-compile-autoload-dotenv (compile.autoloadDotenv: false) inlines process.env.NODE_ENV, process.env.BUN_ENV and process.browser into every file it transpiles at runtime (anything imported from disk rather than embedded in the binary). The values are the ones in the environment at launch, or "development" when unset, and process.browser becomes the literal false.
  • bun run and the same executable built with autoload on (the default) leave these as live reads, so in the autoload-off executable a later process.env.NODE_ENV = ... is invisible to disk-loaded files, and the "development" default gets baked in when NODE_ENV is unset. The same happens on the main thread and in workers.
  • Cause: apply_standalone_runtime_flags (src/bun.js.rs:21) switches options.env.behavior to DotEnvBehavior::disable when the executable carries the DISABLE_DEFAULT_ENV_FILES flag. defines_from_transform_options (src/bundler/options.rs:887) adds the NODE_ENV / BUN_ENV / process.browser defines for every behavior except LoadAllWithoutInlining; disable only skips the copy of the other env vars. disable is the bundler's --env=disable setting; whether the bundler should keep inlining NODE_ENV under it is a separate open question (Bun.build unexpectedly inlines NODE_ENV when env: "disable" flag is set #20183, Bun is not honoring env option during build but only for NODE_ENV #22820, Bun.build: honor env: "disable" for process.env.NODE_ENV #35952, with fix(bundler): honor env: disable for process.env.NODE_ENV #35954 and bun build --compile: read NODE_ENV at runtime, set argv[0] to the executable path #32851 proposing changes to the shared function) that this PR does not touch: the runtime inside an executable should not be running with a bundler setting in the first place, and no other runtime path does.
  • Repro (bun 1.4.0): entry.ts sets process.env.NODE_ENV = "changed" and dynamically imports a file from the cwd that prints process.env.NODE_ENV; bun run and the default executable print changed, the --no-compile-autoload-dotenv executable prints development.

Fix

  • apply_standalone_runtime_flags always keeps LoadAllWithoutInlining and only sets disable_default_env_files from the flag. run_env_loader passes that flag to Loader::load, which then skips the default .env files; process env vars are still loaded, and NODE_ENV=production still switches production mode, exactly as before.
  • Correct because this is the combination bun --no-env-file already runs with (src/runtime/cli/Arguments.rs:986 sets the same field, boot in run_command.rs sets the same behavior), so an autoload-off executable now behaves like bun --no-env-file <entry>, and identically to an autoload-on executable apart from the .env files. For executables built with autoload on (flag clear) the function produces the same values as before, so they are unaffected.
  • One consequence of the same alignment: an autoload-off executable given an explicit --env-file=... at runtime (via BUN_OPTIONS or --compile-exec-argv) now loads that file, as bun --no-env-file --env-file=... and autoload-on executables do; previously the disable branch ignored it. The autoloaded .env* files stay skipped.
  • Main thread and workers share apply_standalone_runtime_flags, so both are fixed by the one change.
  • Verified: test/bundler/bundler_compile_autoload.test.ts gets compile/AutoloadDotenvDisabledDoesNotInlineNodeEnvCLI, which builds one autoload-off executable whose main thread and worker each assign the three values and then import a package from the cwd's node_modules that reads them back. It fails on bun 1.4.0 (prints ["from-launch-env","from-launch-env",false] twice) and passes with this change; the same program with autoload on already passes on 1.4.0, which is the parity the test pins.
  • The rest of bundler_compile_autoload.test.ts (24 tests, including the autoload-off .env and worker cases that guard the opt-out itself) passes on the debug build. test/regression/issue/27431.test.ts covers the autoload-off worker configuration on Windows and runs in CI.

Background

  • Standalone executables embed the bundled entry points, but still carry the full runtime: files imported from disk at runtime (node_modules next to the cwd, dynamic imports of absolute paths) go through the runtime transpiler like they would under bun run.
  • DotEnvBehavior is the transpiler's env setting, shared between bun build and the runtime. LoadAll / prefix inline env vars into the code (bundler --env=inline, --env=PREFIX_*), disable inlines nothing except the NODE_ENV / BUN_ENV / process.browser defaults (bundler --env=disable, also the struct default), and LoadAllWithoutInlining is the runtime setting: load env files into process.env, inline nothing. The runtime (bun run, bun test, workers, executables) always uses the last one.
  • disable_default_env_files is a separate option that only controls whether Loader::load reads the automatic .env, .env.local, .env.{development,production,test} files. It is what --no-env-file and bunfig env = false set; the executable's DISABLE_DEFAULT_ENV_FILES flag is the compiled-in form of it.
  • apply_standalone_runtime_flags is called once per VM in an executable, for the main thread (RunCommand::boot_standalone) and for each worker (WebWorker::start_vm), right before configure_defines builds the define table from these options.
  • Related: Key the runtime transpiler cache on the define table, the module type and --jsx-side-effects #38590 keys the shared on-disk transpiler cache on the define table, which is where these entries were noticed; it does not change what gets inlined. Stop inlining process.env dot-reads in Worker-thread transpiles #34211 fixes a different inlining bug in non-standalone workers. fix(bundler): honor env: disable for process.env.NODE_ENV #35954 and bun build --compile: read NODE_ENV at runtime, set argv[0] to the executable path #32851 change what bun build inlines; even with either of them, an autoload-off executable would still run with the bundler setting (still inlining process.browser with fix(bundler): honor env: disable for process.env.NODE_ENV #35954, still ignoring --env-file), so this change is needed independently of them.

An executable built with --no-compile-autoload-dotenv (compile.autoloadDotenv:
false) switched the runtime transpiler's env behavior to `disable`. Every
behavior other than LoadAllWithoutInlining makes defines_from_transform_options
add process.env.NODE_ENV, process.env.BUN_ENV and process.browser to the define
table, so any file the executable transpiled at runtime had the values from the
launch environment (or "development") baked in, unlike `bun run` and unlike the
same executable built with autoload on.

Keep LoadAllWithoutInlining and express the opt-out through
disable_default_env_files, which is how `bun --no-env-file` does it. This
applies to the main thread and to workers, which share
apply_standalone_runtime_flags.
@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: 7 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: 72230c28-00a6-4c0b-85e3-6a23648cfef1

📥 Commits

Reviewing files that changed from the base of the PR and between 9cff2a1 and a86877c.

📒 Files selected for processing (2)
  • src/bun.js.rs
  • test/bundler/bundler_compile_autoload.test.ts

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

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. fix(bundler): honor env: disable for process.env.NODE_ENV #35954 - Edits the same branch in defines_from_transform_options to skip the NODE_ENV/BUN_ENV defines when env_behavior == Disable, the competing shared-function fix for the same defect this PR routes around from the caller side.
  2. bun build --compile: read NODE_ENV at runtime, set argv[0] to the executable path #32851 - Rewrites the same define block to gate the default NODE_ENV/BUN_ENV define on !target.is_server_side(), resolving the same user-visible symptom of NODE_ENV being frozen into bun build --compile binaries.

🤖 Generated with Claude Code

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced on bun 1.4.0 with bun build --compile --no-compile-autoload-dotenv plus a dynamically imported file from the cwd: it prints the inlined development, while bun run and the autoload-on executable print the live value. Fix and test are in this PR; the new case in test/bundler/bundler_compile_autoload.test.ts fails on 1.4.0 and passes on the debug build. Waiting on CI.

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Looked at both; neither covers this one.

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