ErrLookupBackground articles › "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours

"is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours

"Invalid env var value" errors and warnings appear when a library reads an environment variable and finds it set to something it cannot accept — a non-integer like '4096ms' or '16k', a negative number where only positives work, a misspelled enum value, or a variable that was never substituted from a template. Depending on the library, the result is a startup abort, a thrown exception at first use, or an advisory warning with a silent fallback to a default. This article explains the common validation patterns across 48 open-source projects, why values like 'yes', '5000ms', 'V2', or a missing os.environ/ reference get rejected, and how to fix and prevent them.

Distilled from 99 documented records across 48 repositories.

Background

Almost every library that reads configuration from the environment faces the same problem: env vars arrive as raw strings, and process.env / std::env / ENV carry no type information. So the library has to parse and validate each variable at some point — at boot, at first use, or at configuration time — and this family of errors is what you see when that validation fails. The trigger is nearly always a mismatch between what you wrote and what the parser accepts: plain integers where unit suffixes ('5000ms', '64MB', '16k') were used, exact lowercase enums where you typed 'V2', 'CUDA', 'AlwaysOn', or 'JSON', strict boolean literals where you wrote 'yes' or 'on' (k6 accepts only true/false/1/0 via strconv.ParseBool), or an empty/whitespace value from an unfilled .env template.

What happens after the rejection varies, and it is the single most important axis for debugging. Three behaviors show up across the records. Some libraries fail fast: Deno aborts startup on a bad NODE_CHANNEL_SERIALIZATION_MODE, NocoBase throws during plugin beforeLoad, mastra throws a config error naming the variable, range, and raw value, and Zed panics naming the variable and bad value. Others are advisory: AnythingLLM, MLflow's TypeScript core, and context7 print a console warning and keep running with a default — your custom value is silently ignored, which can be more dangerous than a crash because the only evidence is a log line. GitNexus is explicitly mixed: env-set thresholds soft-fall back while the equivalent CLI flag hard-errors, a deliberate design to preserve 'set once in your shell' ergonomics.

A second axis is when validation fires. Some checks run at process startup before anything else happens. Others are lazy: litellm resolves 'os.environ/SLACK_WEBHOOK_URL_1' only when an alert fires, so the real problem (the referenced variable is unset, resolving to NoneType) surfaces far from the config; AnythingLLM's provider token-limit checks throw at provider construction; Codex's CODEX_BWRAP_SHA256 is compiled into the binary via option_env! and evaluated lazily through a OnceLock at first sandbox launch. This means grepping your shell config after the error appears can be misleading — the variable may be read from a container environment block, a cached config (Firefly III's config:cache keeps serving stale values even after you fix .env), or baked in at build time.

Finally, some of these errors are not what they claim to be. Resque intends to raise an informative ArgumentError for a bad FAILURE_BACKEND, but an interpolation bug (referencing an undefined constant instead of the ENV value) turns it into a NameError — so if you see 'uninitialized constant Resque::Failure::FAILURE_BACKEND', the env var is still the culprit. AnythingLLM's token-limit throws say 'No token context limit was set' even though the || 4096 fallback means the only live trigger is a set-but-non-numeric value — the message is misleading dead code from an unreachable branch. RustFS has a documented compat carve-out where an unknown wait-mode value silently falls back to 'auto' unless an explicit local endpoint host is configured. In short: read the library's validation code, not just the message, and trust the error output that echoes the exact rejected value (several, like MLflow and mastra, print the raw value via JSON.stringify for exactly this reason).

Common causes

What usually fixes it

Documented occurrences

…and 79 more across the corpus — use search.

Honest provenance: generated on 2026-09-04 from AI-assisted analysis of the linked records. See how records are made.