Title: child_process: default env ignores a reassigned process.env since v26.11.0 · Issue #66882 · nodejs/node · GitHub
Open Graph Title: child_process: default env ignores a reassigned process.env since v26.11.0 · Issue #66882 · nodejs/node
X Title: child_process: default env ignores a reassigned process.env since v26.11.0 · Issue #66882 · nodejs/node
Description: Version v26.11.0 and later (tested v26.11.1, v26.12.0). v26.10.0, v24.14.1, and v22.23.3 are not affected. Platform Linux x86_64 (official linux-x64 binaries) Subsystem child_process What steps will reproduce the bug? // repro.js const {...
Open Graph Description: Version v26.11.0 and later (tested v26.11.1, v26.12.0). v26.10.0, v24.14.1, and v22.23.3 are not affected. Platform Linux x86_64 (official linux-x64 binaries) Subsystem child_process What steps wil...
X Description: Version v26.11.0 and later (tested v26.11.1, v26.12.0). v26.10.0, v24.14.1, and v22.23.3 are not affected. Platform Linux x86_64 (official linux-x64 binaries) Subsystem child_process What steps wil...
Opengraph URL: https://github.com/nodejs/node/issues/66882
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"child_process: default env ignores a reassigned process.env since v26.11.0","articleBody":"### Version\n\nv26.11.0 and later (tested v26.11.1, v26.12.0). v26.10.0, v24.14.1, and v22.23.3 are not affected.\n\n### Platform\n\n```text\nLinux x86_64 (official linux-x64 binaries)\n```\n\n### Subsystem\n\nchild_process\n\n### What steps will reproduce the bug?\n\n```js\n// repro.js\nconst { spawnSync } = require('node:child_process');\n\nprocess.env = { ...process.env, FOO: 'set' };\n\nconst args = ['-p', 'process.env.FOO'];\nspawnSync(process.execPath, args, { stdio: 'inherit' }); // no options.env\nspawnSync(process.execPath, args, { stdio: 'inherit', env: process.env }); // explicit env\n```\n\n```console\n$ node-v26.10.0 repro.js\nset\nset\n\n$ node-v26.12.0 repro.js\nundefined\nset\n\n$ node-v26.12.0 --permission --allow-child-process --allow-fs-read='*' repro.js 2>/dev/null\nset\nset\n```\n\n### How often does it reproduce? Is there a required condition?\n\nAlways, when `process.env` has been reassigned and a child is spawned without `options.env` while the permission model is off. This affects every `child_process` API (`spawn`, `exec`, `execFile`, `fork`, and the sync variants).\n\nMutating `process.env` in place (`process.env.FOO = 'set'`) is not affected.\n\n### What is the expected behavior? Why is that the expected behavior?\n\nA child spawned without `options.env` should get the same environment as one spawned with `{ env: process.env }`.\n\nThe `child_process` docs list the `env` default as `process.env` for every spawning API, and say \"Use `env` to specify environment variables that will be visible to the new process, the default is `process.env`\" ([L177](https://github.com/nodejs/node/blob/v26.12.0/doc/api/child_process.md?plain=1#L177), [L751](https://github.com/nodejs/node/blob/v26.12.0/doc/api/child_process.md?plain=1#L751)). Command lookup is documented as using `options.env.PATH`, falling back to `process.env.PATH` ([L55](https://github.com/nodejs/node/blob/v26.12.0/doc/api/child_process.md?plain=1#L55)).\n\n`process.env` is writable, and nothing deprecates or warns about reassigning it. `{ env: process.env }` still sees the reassigned object, so the actual default and the documented default now disagree.\n\n`Worker`, `TZ`, and `os.homedir()` read the OS environment, so reassigning `process.env` never affected them. `child_process` is different: its default is documented as `process.env`, and until v26.10.0 it was implemented by copying `process.env`.\n\n### What do you see instead?\n\nThe child gets the OS environment and ignores the reassigned `process.env`. `FOO` is unset, and a reassigned `PATH` is ignored for command lookup.\n\nThe result also depends on `--permission`. With the permission model on, the old code path runs and the child sees `FOO=set`.\n\n### Additional information\n\nCaused by #65325 (\"child_process: build the default env block in one native pass\", first released in v26.11.0). When `!options.env && !permission.isEnabled()`, `normalizeSpawnArguments()` builds `envPairs` with `getEnvPairs()`, which reads the OS environment block, instead of copying `process.env` ([lib/child_process.js L706](https://github.com/nodejs/node/blob/v26.12.0/lib/child_process.js#L706)). Its test (`test-child-process-default-env.js`) only mutates `process.env` in place and never reassigns it.\n\nReassigning `process.env` with a spread copy is a common pattern. [GitHub code search](https://github.com/search?q=%22process.env+%3D+%7B+...process.env%22&type=code) finds a few thousand files containing `process.env = { ...process.env`, for example in build scripts, test setup, and Vite configs. Tools that override `PATH` or `npm_config_*` this way and then spawn without `env` lose the overrides on v26.11+ without any error. We hit this with an npm wrapper that sets `NPM_CONFIG_REGISTRY` and `PATH` this way: its npm child fell back to registry.npmjs.org.\n\nPossible fixes:\n- keep the fast path, but only take it while `process.env` is still the native env proxy. For example, pass `process.env` to `getEnvPairs()` and fall back to the copy path unless `env_proxy_ctor_template()->HasInstance()` is true (the check `node_messaging.cc` already uses). Spreading a reassigned plain object is cheap, so the speedup is kept where it matters;\n- or revert #65325 on v26.x before v26 enters LTS on 2026-10-28. If the new behavior is intended, label it semver-major after the fact so it ships only in v27, and update the `options.env` default and the PATH lookup docs.\n","author":{"url":"https://github.com/everett1992","@type":"Person","name":"everett1992"},"datePublished":"2026-10-20T16:20:39Z","url":"https://github.com/nodejs/node/issues/66882"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:6b800578-ad0b-32ab-4afc-bd3edc14fc68 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | A7CC:3E0336:813EEC:A847D3:6AED3578 |
| html-safe-nonce | 33ad7eb76ea8ef7281b96461a3706d11b2751aba4af249999e1996aeccca5e44 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBN0NDOjNFMDMzNjo4MTNFRUM6QTg0N0QzOjZBRUQzNTc4IiwidmlzaXRvcl9pZCI6IjI0ODYwOTAyMDQxNjY1NjcyODgiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | ad26b2acbcf28fade200626a476913e3314aa657719c28e06d1f87fa70dbe7e9 |
| hovercard-subject-tag | issue:5945362989 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/66882/issue_layout |
| twitter:image | https://opengraph.githubassets.com/2550fd94f88c2bfa4f7ed6d6bc2de94808fcae7b34284a8dfa3fc638f1aefa5e/nodejs/node/issues/66882 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/2550fd94f88c2bfa4f7ed6d6bc2de94808fcae7b34284a8dfa3fc638f1aefa5e/nodejs/node/issues/66882 |
| og:image:alt | Version v26.11.0 and later (tested v26.11.1, v26.12.0). v26.10.0, v24.14.1, and v22.23.3 are not affected. Platform Linux x86_64 (official linux-x64 binaries) Subsystem child_process What steps wil... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | everett1992 |
| hostname | github.com |
| expected-hostname | github.com |
| None | 26c92a9388ddfad3fa88d132b5d5f0b4747030233dcc4e5efb05f1cbae152184 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | c629ad99c254d306375fc8354d04321c2c362236 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width