Skip to content

process.env.<UNSET> is non-nullish, breaking process.env.X ?? default #1312

Description

@proggeramlug

Problem

Reading an unset env var via process.env.FOO returns a value that is falsy but not nullish, so the idiomatic process.env.FOO ?? 'default' does not apply the default. This silently produced a schemeless URL in a server I was building, and fetch then failed with the opaque Fetch error: builder error.

Repro (perry 0.5.1022)

const v = process.env.DEFINITELY_UNSET_VAR_XYZ;
console.log('typeof:', typeof v);              // typeof: string
console.log('value:', JSON.stringify(v));      // value: null
console.log('?? :', JSON.stringify(v ?? 'FALLBACK'));  // ?? : null   (no fallback!)
console.log('|| :', JSON.stringify(v || 'FALLBACK'));  // || : "FALLBACK"

So:

  • typeof says string
  • JSON.stringify says null
  • ?? does not treat it as nullish → no fallback
  • || does treat it as falsy → fallback works

Node's behavior: process.env.UNSET is undefined, and undefined ?? 'x''x'.

Impact

Any code using the extremely common process.env.X ?? default pattern gets a wrong value for unset vars. The downstream failure is far away and opaque (in my case a reqwest builder error from a schemeless URL three call-frames later). This is a sharp edge: the code looks correct and works under Node/Bun, then misbehaves only when compiled by Perry with the var unset.

Expected

process.env.<unset> should be undefined (nullish), so ?? falls back. At minimum the typeof/JSON.stringify/?? results should agree with each other.

Workaround

Use process.env.X || default instead of ?? default. Documented this in my codebase, but ?? is the more correct operator (it shouldn't swallow a legitimately empty-string env var) so the runtime behavior is the thing to fix.

Found while building playground.perryts.com.

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