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.
Problem
Reading an unset env var via
process.env.FOOreturns a value that is falsy but not nullish, so the idiomaticprocess.env.FOO ?? 'default'does not apply the default. This silently produced a schemeless URL in a server I was building, andfetchthen failed with the opaqueFetch error: builder error.Repro (perry 0.5.1022)
So:
typeofsaysstringJSON.stringifysaysnull??does not treat it as nullish → no fallback||does treat it as falsy → fallback worksNode's behavior:
process.env.UNSETisundefined, andundefined ?? 'x'→'x'.Impact
Any code using the extremely common
process.env.X ?? defaultpattern 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 beundefined(nullish), so??falls back. At minimum thetypeof/JSON.stringify/??results should agree with each other.Workaround
Use
process.env.X || defaultinstead 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.