Skip to content

child_process.execFileSync uses stale PATH for command resolution when options.env is omitted #29237

@CorticalCode

Description

@CorticalCode

What version of Bun is running?

1.3.12+700fc117a

What platform is your computer?

Darwin 25.4.0 arm64 arm

What steps can reproduce the bug?

In Bun, execFileSync appears to resolve bare command names using a snapshot of the environment taken at process start. If process.env.PATH is mutated at runtime, Bun continues to resolve commands using the original PATH unless options.env is explicitly provided.

In Node.js, execFileSync honors runtime mutations to process.env.PATH by default, even when the options argument is omitted.

const { execFileSync } = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');

// 1. Create a "fake" binary in a temp directory
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bun-path-test-'));
const fakeBinaryPath = path.join(tmpDir, 'ls');
fs.writeFileSync(fakeBinaryPath, '#!/bin/sh\necho "FAKE_CALLED"', { mode: 0o755 });

// 2. Prepend the temp directory to PATH at runtime
const originalPath = process.env.PATH;
process.env.PATH = `${tmpDir}${path.delimiter}${originalPath}`;

console.log(`Current process.env.PATH starts with: ${process.env.PATH.split(path.delimiter)[0]}`);

try {
  // 3. Attempt to call 'ls' without an explicit env option
  // Expected (Node.js behavior): Resolves to our fake binary and prints "FAKE_CALLED"
  // Actual (Bun behavior): Resolves to /bin/ls and prints real directory listing
  const output = execFileSync('ls', { encoding: 'utf8' }).split('\n')[0];
  console.log(`Result (omitted env): ${output}`);

  // 4. Attempt to call 'ls' with explicit env: process.env
  // This works in both runtimes
  const outputWithEnv = execFileSync('ls', { encoding: 'utf8', env: process.env }).split('\n')[0];
  console.log(`Result (explicit env): ${outputWithEnv}`);

} finally {
  // Cleanup
  fs.rmSync(tmpDir, { recursive: true, force: true });
}

What is the expected behavior?

Current process.env.PATH starts with: /tmp/bun-path-test-XXXX
Result (omitted env): FAKE_CALLED
Result (explicit env): FAKE_CALLED

Both calls should resolve to the fake binary since process.env.PATH was updated before either call.

What do you see instead?

Current process.env.PATH starts with: /tmp/bun-path-test-XXXX
Result (omitted env): [Real file list from /bin/ls]
Result (explicit env): FAKE_CALLED

The call without explicit env resolves to the real /bin/ls instead of the fake binary prepended to PATH.

Additional information

  • Workaround: Passing env: process.env explicitly makes Bun behave like Node.js.
  • Behavior difference: Node's default inherited environment reflects runtime process.env mutations for command lookup. Bun appears to use a stale snapshot of the environment for the initial binary resolution unless a fresh environment object is passed.
  • Scope: This has been observed with execFileSync. Further testing may be required for spawn, exec, and other child_process variants.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions