You put DEBUG=false in .env, restart, and the app is still in debug mode. If you have searched "environment variable false is true" or "os.getenv boolean", here is the whole explanation: an environment variable is always a string, and "false" is a non-empty string, so it is truthy. Python's os.getenv, Node's process.env and Vite's import.meta.env all hand you text, never a bool or a number.
DEBUG = bool(os.getenv("DEBUG")) # True for DEBUG=false
WORKERS = int(os.getenv("WORKERS")) # TypeError when WORKERS is unset
if (process.env.FEATURE_BETA) enableBeta(); // runs for FEATURE_BETA=false
const next = process.env.PORT + 1; // "30001", not 3001
(A made-up example, but the same bug as the Django forum thread "ENV variables can never seem to be False" and python-dotenv #291.)
The traps
-
Truthiness:
if os.getenv("DEBUG"):,!!process.env.FLAG,bool(...): true for"false","0"and"no". -
Boolean literals:
process.env.FLAG === trueandos.getenv("X") == Trueare always false. -
Numbers:
"3000" + 1is"30001"in JS; in Pythonos.getenv("PORT") + 1raisesTypeError. -
Unset values:
int(os.getenv("X"))andJSON.parse(process.env.X)crash whenXis missing. -
Mixed defaults:
os.environ.get("RETRIES", 3)is astrwhen set and anintwhen not.
The fix
Read a string, convert once:
DEBUG = os.getenv("DEBUG", "").strip().lower() in ("1", "true", "yes", "on")
WORKERS = int(os.getenv("WORKERS", "4"))
const debug = ["1", "true", "yes", "on"].includes((process.env.DEBUG ?? "").trim().toLowerCase());
const port = Number(process.env.PORT ?? "3000");
Defaults should be strings too, so there is only ever one type to convert. Presence checks such as if not os.getenv("API_KEY"): raise are fine. Only flags need parsing.
A linter for it in VS Code
I built EnvTruth, a free VS Code extension for Python and JS/TS. Five rules (ET001 to ET005) cover the traps above, and each finding has a Quick Fix that writes the parse for you. Run on the snippet above, it reports DEBUG is a string, so "false", "0" and "no" are all truthy and "PORT + 1" concatenates ("3000" + 1 is "30001") instead of adding.
It has its own activity bar icon with a badge and an Env checks panel grouped by file, and hovering an env read shows where the variable is defined (.env, compose, Kubernetes) with secrets masked. It skips presence checks on names like API_KEY and DATABASE_URL, and treats any variable your .env sets to true/false/0/1 as a flag. No network, no telemetry, and it never runs a process.
code --install-extension jaytankdev.envtruth
Source (MIT): github.com/jay-tank/envtruth
Full write-up (why env vars are strings in Python, Node and Vite, the number-trap table, all five rules, limitations, FAQ): DEBUG=false but my app still runs in debug mode
Top comments (0)