Bun.spawn/spawnSync, Bun.which: inherit live process.env instead of the startup snapshot - #34972
Bun.spawn/spawnSync, Bun.which: inherit live process.env instead of the startup snapshot#34972robobun wants to merge 7 commits into
Conversation
…he startup snapshot
When no env option is passed, Bun.spawn/Bun.spawnSync serialized the native
DotEnv loader map (populated once from libc environ at startup), and both the
default envp and the PATH used for argv0 lookup ignored any
process.env.X = '...' or delete process.env.X done at runtime. Bun.which read
PATH from the same stale map.
Now the default environment is built by enumerating globalObject->processEnvObject()
through the same append_envp_from_js path that an explicit env: {...process.env}
already took, and Bun.which reads PATH from the same object. Runtime mutations
to process.env, including prepending a directory to PATH, are now visible to the
child and to argv0/which lookup.
WalkthroughChangesLive process.env integration
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Updated 1:04 PM PT - Jul 21st, 2026
✅ @robobun, your commit 3819377efbed49c812ab0f5e291224e16a11619e passed in 🧪 To try this PR locally: bunx bun-pr 34972That installs a local version of the PR into your bun-34972 --bun |
|
Found 2 issues this PR may fix:
🤖 Generated with Claude Code |
|
Regarding the linked issues: #29237 and #11182 both go through #10865 and #25885 are about |
createEnvironmentVariablesMap declares a THROW_SCOPE, so the first call to processEnvObject() (which forces the lazy init) must be followed by an exception check before the next throw scope. Wrap the extern in its own THROW_SCOPE with RETURN_IF_EXCEPTION and route the Rust side through from_js_host_call so zero maps to Err.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/bun/spawn/spawn-env.test.ts`:
- Line 19: Update the affected Bun child tests in the concurrent test blocks to
use sequential test registration: replace test.concurrent with test, and use
test.skipIf where conditional skipping is needed. Preserve each test’s existing
labels, bodies, and skip conditions while ensuring all four spawn cases run
sequentially.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1265ce47-a016-4cc9-be56-1219fa1cad20
📒 Files selected for processing (5)
src/jsc/JSGlobalObject.rssrc/jsc/bindings/ZigGlobalObject.cppsrc/runtime/api/BunObject.rssrc/runtime/api/bun/js_bun_spawn_bindings.rstest/js/bun/spawn/spawn-env.test.ts
Repro
child_process.spawn*was already correct (it always passes an explicitenvbuilt fromprocess.envon the JS side).env: {...process.env}was already correct.Cause
js_bun_spawn_bindings.rs: the no-env:fallback serializedjsc_vm.transpiler.env_mut().map(thebun_dotenv::Loadermap, populated once from libcenvironat startup), and the initial PATH for argv0 lookup came fromjsc_vm.env_loader().get(b"PATH"), the same snapshot.BunObject.rs::which: default PATH came fromvm.env_loader().get(b"PATH").process.env.X = ...(the genericjsSetterEnvironmentVariableinJSEnvironmentVariableMap.cpp) writes only to the JS object, never the native map;delete process.env.Xlikewise.Fix
Expose
ZigGlobalObject::processEnvObject()to Rust and use the liveprocess.envJS object as the default env source:Bun.spawn/spawnSyncwith noenv:now enumeratesprocessEnvObject()via the existingappend_envp_from_js, which also extracts PATH for argv0 lookup. This is the same code pathenv: {...process.env}already exercised.Bun.whichnow reads its default PATH fromprocessEnvObject().PATH.Verification
test/js/bun/spawn/spawn.test.ts,test/js/bun/spawn/spawn-path.test.ts,test/js/bun/util/which.test.ts,test/js/bun/spawn/null-byte-injection.test.tsall pass.no test proof · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/spawn/spawn-env.test.ts