EricDubé.com

JavaScript and Node.js Footguns

July 22, 2026

javascriptnode.jsfootgunsdebuggingscope

JavaScript's flexibility is usually a gift and occasionally a trap — one that hides in plain sight and only springs when a name, a scope, or a coercion lines up just wrong. This is a running collection of those Node.js and JavaScript footguns: bugs that are obvious in hindsight and baffling in the moment.

An internal function named process shadowed the global

Node.js exposes a single global object, process, and reading configuration through process.env is so routine that nobody stops to think about where that object comes from. It comes from the global scope — which means anything that introduces a nearer binding named process silently takes its place.

A background worker did exactly that. It defined a helper to handle one unit of work — an async function process(item) — a name that reads perfectly at the call site. But that declaration created a binding named process in the module scope, shadowing the global. In the same module, a factory read the environment with an ordinary default parameter:

function createRunner(store, opts = {}) {
  const {
    env = process.env,      // resolves the LOCAL `process`, not Node's global
    // ...
  } = opts

  // ...later, once per item:
  const apiKey = env[keyEnv]  // env is undefined -> throws
}

// The shadowing culprit, hoisted into the same scope:
async function process(item) {
  // ...
}

Function declarations are hoisted, so process inside process.env resolved to the local async function process, whose .env property is undefined. env therefore defaulted to undefined, and the first time the code indexed it — env[keyEnv] — it threw:

TypeError: Cannot read properties of undefined (reading 'ANTHROPIC_API_KEY')

The message is a clue in disguise: it names the missing object, not a missing key. The environment variable was set correctly the entire time; the object it was being read from was the wrong one. The fix was a one-line rename — process to processItem — after which process.env resolved to the real global again.

The rule is small and unforgiving: never give a local binding the name of a global that other code depends on. process, window, document, globalThis, require, and module are all fair game for accidental shadowing — and because the shadow is created by a hoisted declaration, the failure surfaces far from the line that caused it, wearing the mask of a missing environment variable.