ErrLookup › Background 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
- Non-integer values for integer variables. The most common trigger across the family: unit suffixes ('5000ms', '64MB', '16k', '4096 tokens'), thousands separators ('16,384'), underscores ('32_768'), decimals ('0.5'), or plain words ('ten') fed to a parser expecting /^[0-9]+$/ or an equivalent. Number()/parseInt-based parsers reject these with NaN; some (like AnythingLLM's parseInt) leniently accept '3x' as 3, which makes a strict lint all the more important.
- Enum values that are misspelled, wrong-cased, or stale. Many env vars are enums with exact accepted strings: FIREFLY_III_LAYOUT must be 'v1'/'v2', CLAUDE_MEM_QUEUE_ENGINE 'sqlite'/'bullmq', GITNEXUS_EMBEDDING_DEVICE one of five lowercase backends, RUST_LOG_FORMAT 'json'/'pretty', __EXPO_CONFIG_MODE 'development'/'production'. Case matters almost everywhere ('V2', 'CUDA', 'JSON', 'AlwaysOn' all fail), and hyphens vs underscores matter ('fail_fast' is rejected where 'fail-fast' and 'failfast' both work).
- Values from .env files, templates, or shell quoting polluted the value. Surrounding quotes, inline comments ('131072 # claude'), trailing whitespace, CRLF from Windows-edited files, BOM, and unfilled template tokens (${VAR}, null, -) all make a value fail a strict parse even though it looks right on screen. Firefly III adds a twist: a cached config can keep serving an old bad value after .env is fixed until you run config:clear.
- Zero, negative, or out-of-range numbers where positivity/range is required. Positive-integer validators (MLflow readPositiveInt, GitNexus parsePositiveInt, context7 subscriptions) reject 0, -1, and negatives; non-negative validators accept 0 but reject -1. Range checks go further: NocoBase requires the temporary file access lifetime to be between 5m and 10m with only s/m units, and mastra enforces per-variable minimums and a 2147483647 cap.
- Referenced or derived variables that do not exist. Indirection fails when the referenced name is missing: litellm's 'os.environ/SLACK_WEBHOOK_URL_1' resolves to None when the variable is unset (or misspelled) in the proxy process, producing a type error disguised as a value error. Similarly, an env-declared cache directory in GitNexus must already exist as a real, non-symlink directory — the code deliberately never creates it.
- Wrong variable or confused siblings. Putting a URL where an enum belongs (CAVEMAN_BEDROCK_ENDPOINT accepts only 'runtime'/'mantle', not a gateway URL), mixing up sibling variables (ANYTHINGLLM_MAX_RETRIES vs ANYTHINGLLM_FETCH_TIMEOUT), or using a convention from another tool ('yes'/'on' booleans for k6, a naive 'linux_x86_64' platform tag for llmfit, samplers other OTel SDKs support in Deno).
- Values valid in one version but not another. Upgrades and cross-version deployments carry stale vocab: Caveman notes values from older versions that accepted different names, RustFS sees typo'd or renamed values from chart upgrades, Argo allowlist entries that no longer exist in your version, and claude-mem sees docker-compose examples whose values the installed version rejects.
What usually fixes it
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
Documented occurrences
- ${LOG_PREFIX} ANYTHINGLLM_MAX_RETRIES="${envDefinedMaxRetries}" is not a valid non-negative integer — using default ${DEFAULT_MAX_RETRIES}. (Mintplex-Labs/anything-llm)
- invalid failure backend: #{FAILURE_BACKEND} (resque/resque)
- [mlflow] Ignoring invalid ${envName}=${JSON.stringify(raw)}; expected a positive integer. Falling back to default ${fallback}. (mlflow/mlflow)
- Invalid serialization type: {} (denoland/deno)
- ${TRUSTED_CACHE_DIRECTORY_ENV} must name a pre-existing protected non-symlink directory (abhigyanpatwari/GitNexus)
- CAVEMAN_BEDROCK_ENDPOINT must be runtime or mantle (JuliusBrussee/caveman)
- TEMPORARY_FILE_ACCESS_EXPIRES_IN must be between 5m and 10m (nocobase/nocobase)
- Unknown LLMFIT_PYTHON_PLATFORM_TAG={py_target!r}. Must be one of: {sorted(TARGET_CONFIGS)} (AlexsJones/llmfit)
- ${name} must be an integer ${range}; received ${JSON.stringify(raw)}. (mastra-ai/mastra)
- Invalid {SERVER_ENV_PREFIX}ALLOW_FS_TOOLS value: unknown filesystem tool name(s) {unknown!r}; valid names are {sorted(valid_names)}. (langchain-ai/deepagents)
- [mlflow] Ignoring invalid ${envName}=${JSON.stringify(raw)}; expected a non-negative integer. Falling back to default ${fallback}. (mlflow/mlflow)
- No token context limit was set. (Mintplex-Labs/anything-llm)
- Invalid layout configuration (firefly-iii/firefly-iii)
- Invalid MCP_MAX_SUBSCRIPTIONS; using the default of ${DEFAULT_MAX_SUBSCRIPTIONS}. (upstash/context7)
- Env var OTEL_TRACES_SAMPLER specifies an unsupported sampler: {} (denoland/deno)
- BAD_ARGS: Invalid __EXPO_CONFIG_MODE value. (expo/expo)
- No LocalAi token context limit was set. (Mintplex-Labs/anything-llm)
- Invalid webhook url value for: {webhook_url}. Got type={type(_env_value)} (BerriAI/litellm)
- invalid CODEX_BWRAP_SHA256 value: {err} (openai/codex)
- invalid {ENV_STARTUP_TOPOLOGY_WAIT_MODE}; expected auto, orchestrated, bounded, fail-fast, failfast, or strict (rustfs/rustfs)
…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.