The pattern
An environment variable is missing, renamed, differently cased, or scoped to the wrong environment, so the code silently falls back to a default that is only correct on one machine. These bugs feel random because the code is identical everywhere - only the environment differs. The failure usually lives in the gap between where config is read and where it is validated.
( 01 )Symptoms
How this failure announces itself.
- warningTests pass locally but fail in CI, staging, or production with no code change in between.
- warningThe service starts cleanly but behaves as if a feature flag, secret, or endpoint was never configured.
- warningA secret-dependent path (auth, billing, webhooks) returns the wrong status in exactly one environment.
- warningLogs show a default value being used where you expected an injected one.
( 02 )First moves
The first ten minutes — establish facts before touching code.
- 1Log the resolved config the service actually booted with in the failing environment - not what you think it received.
- 2Diff that resolved config against a working environment; the divergent key is usually visible immediately.
- 3Trace the divergent key backwards: who sets it, who forwards it, who reads it, and which fallback applies when it is absent.
- 4Check for case, prefix, and naming drift between the setter and the reader (APP_API_URL vs API_URL).
- 5Reproduce locally by unsetting the variable - if that does not reproduce the failure, the variable is not the cause.
( 03 )Where to look
The code and config that usually owns this bug.
- searchThe runtime config loader - every place that reads process.env (or equivalent) and what it falls back to when the key is absent.
- searchCI and deployment env definitions - compare the exact key names against what the code reads, character by character.
- searchContainer and orchestrator env mapping - a variable set in CI does not automatically reach the container runtime.
- searchCase-sensitive key or file references - LOG_LEVEL vs log_level, .env vs .env.local load order.
( 04 )Common fixes
Fix the cause, then make the regression impossible.
- buildValidate required env at startup and crash loudly with the missing key's name instead of limping along on defaults.
- buildUse one canonical key name per value, read in exactly one module, so renames happen in a single place.
- buildMake fallbacks explicit and environment-aware - a default that is safe locally can be wrong in production.
- buildAdd a regression test that runs the config loader with the variable unset and asserts the visible failure.
( 05 )Prove the fix
A fix you can't demonstrate is a guess. Close the loop.
- verifiedBoot the service with the fix in the previously failing environment and confirm the resolved config matches expectations.
- verifiedUnset the variable on purpose and confirm the service now fails fast with a clear message instead of misbehaving.
- verifiedRe-run the originally failing path and watch it pass for the stated reason, not by accident.