Skip to content

worker_threads not respecting --require in process.execArgv or process.env.NODE_OPTIONS #37410

Closed as not planned

Description

@clemyan
  • Versions:
    • v12.20.2
    • v14.15.5
    • v15.8.0
  • Platforms:
    • CentOS 7 (Linux 3.10.0-1127.8.2.el7.x86_64 #1 SMP Tue May 12 16:57:42 UTC 2020 x86_64 GNU/Linux)
    • Windows 10 (Microsoft Windows NT 10.0.19041.0 x64)
  • Subsystem: worker_threads

What steps will reproduce the bug?

Mutate either process.env.NODE_OPTIONS or process.execArgv to include a --require flag before spawning a worker thread.

Test script
const { Worker, isMainThread, SHARE_ENV } = require('worker_threads')
const { once } = require('events')

if (module.parent && module.parent.id === 'internal/preload') {
  // Preload
  delete require.cache[__filename]
  console.log('preloaded')
} else if (isMainThread && !process.send) {
  // Main
  const tests = [
    async () => {
      console.log('worker process.execArgv')
      const orig = process.execArgv
      process.execArgv = ['--require', __filename, ...process.execArgv]
      const worker = new Worker(__filename)
      await once(worker, 'exit')
      process.execArgv = orig
    },
    async () => {
      console.log('worker process.env.NO')
      const orig = process.env.NODE_OPTIONS
      process.env.NODE_OPTIONS = `--require ${__filename} ${process.env.NODE_OPTIONS}`
      const worker = new Worker(__filename)
      await once(worker, 'exit')
      process.env.NODE_OPTIONS = orig
    },
    async () => {
      console.log('worker SHARE_ENV')
      const orig = process.env.NODE_OPTIONS
      process.env.NODE_OPTIONS = `--require ${__filename} ${process.env.NODE_OPTIONS}`
      const worker = new Worker(__filename, { env: SHARE_ENV })
      await once(worker, 'exit')
      process.env.NODE_OPTIONS = orig
    },
  ]
  ;(async () => {
    for (const f of tests) {
      await f()
      console.log()
    }
  })()
}

What is the expected behavior?

The --require flag is respected in each case:

worker process.execArgv
preloaded

worker process.env.NO
preloaded

worker SHARE_ENV
preloaded

What do you see instead?

The --require flag is not respected:

worker process.execArgv

worker process.env.NO

worker SHARE_ENV

Additional information

tl;dr: Seems like the default value of env and execArgv options of the Worker constructor is the initial value of process.env and process.execArgv instead of the current value. This behavior is in contrast to child_process.fork.

Full test script

This is the full test script with all the tests I have done (detailed below).

Test script
const { Worker, isMainThread, SHARE_ENV, parentPort } = require('worker_threads')
const { fork } = require('child_process')
const { once } = require('events')

if (module.parent && module.parent.id === 'internal/preload') {
  // Preload
  delete require.cache[__filename]
  console.log('preloaded')
} else if (isMainThread && !process.send) {
  // Main
  const tests = [
    async () => {
      console.log('worker control')
      const worker = new Worker(__filename)
      await once(worker, 'exit')
    },
    async () => {
      console.log('worker process.execArgv')
      const orig = process.execArgv
      process.execArgv = ['--require', __filename, ...process.execArgv]
      const worker = new Worker(__filename)
      await once(worker, 'exit')
      process.execArgv = orig
    },
    async () => {
      console.log('worker execArgv option')
      const worker = new Worker(__filename, { execArgv: ['--require', __filename, ...process.execArgv] })
      await once(worker, 'exit')
    },
    async () => {
      console.log('worker process.env.NO')
      const orig = process.env.NODE_OPTIONS
      process.env.NODE_OPTIONS = `--require ${__filename} ${process.env.NODE_OPTIONS}`
      const worker = new Worker(__filename)
      await once(worker, 'exit')
      process.env.NODE_OPTIONS = orig
    },
    async () => {
      console.log('worker SHARE_ENV')
      const orig = process.env.NODE_OPTIONS
      process.env.NODE_OPTIONS = `--require ${__filename} ${process.env.NODE_OPTIONS}`
      const worker = new Worker(__filename, { env: SHARE_ENV })
      await once(worker, 'exit')
      process.env.NODE_OPTIONS = orig
    },
    async () => {
      console.log('worker env option')
      const worker = new Worker(__filename, { env: { NODE_OPTIONS: `--require ${__filename} ${process.env.NODE_OPTIONS}` } })
      await once(worker, 'exit')
    },

    async () => {
      console.log('fork control')
      const cp = fork(__filename)
      await once(cp, 'exit')
    },
    async () => {
      console.log('fork process.execArgv')
      const orig = process.execArgv
      process.execArgv = ['--require', __filename, ...process.execArgv]
      const cp = fork(__filename)
      await once(cp, 'exit')
      process.execArgv = orig
    },
    async () => {
      console.log('fork execArgv option')
      const cp = fork(__filename, { execArgv: ['--require', __filename, ...process.execArgv] })
      await once(cp, 'exit')
    },
    async () => {
      console.log('fork process.env.NO')
      const orig = process.env.NODE_OPTIONS
      process.env.NODE_OPTIONS = `--require ${__filename} ${process.env.NODE_OPTIONS}`
      const cp = fork(__filename)
      await once(cp, 'exit')
      process.env.NODE_OPTIONS = orig
    },
    async () => {
      console.log('fork env option')
      const cp = fork(__filename, { env: { NODE_OPTIONS: `--require ${__filename} ${process.env.NODE_OPTIONS}` } })
      await once(cp, 'exit')
    },
  ]
  ;(async () => {
    for (const f of tests) {
      await f()
      console.log()
    }
  })()
}
Expected output
worker control

worker process.execArgv
preloaded

worker execArgv option
preloaded

worker process.env.NO
preloaded

worker SHARE_ENV
preloaded

worker env option
preloaded

fork control

fork process.execArgv
preloaded

fork execArgv option
preloaded

fork process.env.NO
preloaded

fork env option
preloaded
Actual output
worker control

worker process.execArgv

worker execArgv option
preloaded

worker process.env.NO

worker SHARE_ENV

worker env option
preloaded

fork control

fork process.execArgv
preloaded

fork execArgv option
preloaded

fork process.env.NO
preloaded

fork env option
preloaded

Running node with --require affects worker threads

Modify the test script to use the following test case

async () => {
  console.log('worker control')
  const worker = new Worker(__filename)
  await once(worker, 'exit')
}

Then run the script with node --require ./main.js main.js outputs:

preloaded
worker control
preloaded

which shows the --require flag is respected in both the main thread and the worker thread

--require in Worker options is respected

When explicitly passing execArgv or env.NODE_OPTIONS to the Worker constructor options, the --require flag is respected:

async () => {
  console.log('worker execArgv option')
  const worker = new Worker(__filename, { execArgv: ['--require', __filename, ...process.execArgv] })
  await once(worker, 'exit')
},
async () => {
  console.log('worker env option')
  const worker = new Worker(__filename, { env: { NODE_OPTIONS: `--require ${__filename} ${process.env.NODE_OPTIONS}` } })
  await once(worker, 'exit')
},

The above logs:

worker execArgv option
preloaded

worker env option
preloaded

child_process.fork respects --require in process.execArgv or process.env.NODE_OPTIONS

const { fork } = require('child_process')

async () => {
  console.log('fork process.execArgv')
  const orig = process.execArgv
  process.execArgv = ['--require', __filename, ...process.execArgv]
  const cp = fork(__filename)
  await once(cp, 'exit')
  process.execArgv = orig
},
async () => {
  console.log('fork process.env.NO')
  const orig = process.env.NODE_OPTIONS
  process.env.NODE_OPTIONS = `--require ${__filename} ${process.env.NODE_OPTIONS}`
  const cp = fork(__filename)
  await once(cp, 'exit')
  process.env.NODE_OPTIONS = orig
},

The above logs:

fork process.execArgv
preloaded

fork process.env.NO
preloaded

Activity

  1. added
    workerIssues and PRs related to the worker_threads module and Worker API.
    on Feb 17, 2021
  2. aduh95 commented on Feb 17, 2021

    @aduh95
    Contributor

    Related? #36531

  3. clemyan commented on Feb 17, 2021

    @clemyan
    Author

    Not really. As far as I understand the comment thread, #36531 occurs because --require passed to the node process is respected by workers (which is intended and expected). This issue is about how --require is not respected (by default) by workers if not passed to the node process.

    In order words:

    --require flag is ... new Worker cp.fork
    ... passed to node process (e.g. node -r ... ) Respected Respected
    ... added to process.execArgv or process.env Not respected Respected
    ... passed explicitly to Worker/fork Respected Respected

    #36531 is about the top-left "Respected" cell. This issue is about the "Not respected" cell.

  4. aduh95 commented on Feb 17, 2021

    @aduh95
    Contributor

    //cc @nodejs/workers

  5. clemyan commented on Feb 18, 2021

    @clemyan
    Author

    A little more investigation.

    The discrepancy between Worker vs cp.fork is probably because the default values of execArgv and env are resolved in JS for cp.fork (here and here) while they are resolved in C++ for Worker (here and here).

  6. github-actions commented on Jun 27, 2026

    @github-actions
    Contributor

    This issue has been marked as stale due to 210 days of inactivity.
    It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.

  7. added
    staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.
    on Jun 27, 2026
  8. github-actions commented on Jul 28, 2026

    @github-actions
    Contributor

    This issue has been automatically closed after 30 days of inactivity following its stale status (no activity for a total of 120 days).
    If this is still relevant, feel free to reopen it or leave a comment with additional details so we can continue the discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.workerIssues and PRs related to the worker_threads module and Worker API.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions