Never Cast an Env Var to Bool
This one-line config bug turns your kill switch on. Environment variables are always strings, and in almost every language a non-empty string is truthy — so bool(os.environ["KILL_SWITCH"]) returns True whether the operator set it to "true", "false", or "0". The flag you built to stop the system just became a flag that can't say no.
Why the raw cast lies
Python's truth-value rule is about emptiness, not meaning: an object is true unless it's one of the documented falsy values — None, False, numeric zero, or an empty collection like ''. The string "false" has five characters, so it's truthy. The same trap exists across the stack:
- Python:
bool("false")→True. Only""is falsy. - JavaScript/Node:
process.envvalues are strings;Boolean("false")→true, andif (process.env.FLAG)passes for"0"and"false"alike. - Ruby: every string is truthy — even
"false"and"".
There are exactly two string values a raw cast handles correctly: unset and empty. Everything an operator would actually type — true, false, 1, 0, yes, no — is either mishandled or half-handled.
Parse through one shared vocabulary
The fix is a single parser with an explicit true/false vocabulary, used for both env vars and structured config, so false means false everywhere:
_TRUE = {"1", "true", "t", "yes", "y", "on"}
_FALSE = {"0", "false", "f", "no", "n", "off"}
def env_bool(name: str, default: bool = False) -> bool:
raw = os.environ.get(name)
if raw is None or raw.strip() == "":
return default # blank template value != forced off
v = raw.strip().lower()
if v in _TRUE:
return True
if v in _FALSE:
return False
raise ValueError(f"{name}={raw!r} is not a boolean")
Three properties matter more than the exact word list:
| Property | Why it matters |
|---|---|
| Unset/blank → default | An empty FLAG= line in a .env template shouldn't force the flag off (or on) |
| Unknown value → error | FLAG=fales should crash at startup, not silently pick a side |
| One vocabulary everywhere | Env vars, YAML, and CLI flags must agree on what "false" is |
That last row is the subtle one. If your env parser accepts yes but your YAML loader doesn't, the same deployment value flips meaning depending on where it's written.
Prior art: what real parsers accept
- Go —
strconv.ParseBoolaccepts exactly1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False; anything else returns an error. That's the whole design in one function signature: closed vocabulary, loud failure. - Pydantic — validates a
boolfield from a string that lowercases to one of0, off, f, false, n, no, 1, on, t, true, y, yes— the same shared-vocabulary idea, applied to structured config. - Python's old
distutils.util.strtobooldid this too — butdistutilswas removed in Python 3.12 (PEP 632), which is exactly why so many codebases grew a hand-rolled (and often buggy) replacement. Write the ten-line version above once, or take Pydantic's.
The twelve-factor methodology pushes config into env vars precisely because they're a language- and OS-agnostic standard — which is also why they can only carry strings. The type discipline is your job.
Power-user notes
- Fail at startup, not at use. Parse every boolean flag when the process boots and crash on garbage. A
ValueErrorat deploy time is a log line; a misread kill switch at 3 a.m. is an incident. - Grep for the bug.
bool(os.environ,Boolean(process.env, and bareif (process.env.X)are all findable in one search. Also flag string comparisons likeenv == "True"— they break the moment someone writestrue. - Watch AI-generated code. Assistants frequently emit
os.environ.get("DEBUG", False)and then truthy-test it — a review-time check ("every env boolean goes throughenv_bool") catches this class of bug mechanically. - Beware
FLAG=0. Ops muscle memory writes0for off. A parser that only acceptstrue/falsereads"0"as an error at best — include the numeric pair.
Resources
- Truth Value Testing — Python docs
strconv.ParseBool— Go standard library- Booleans — Pydantic standard-library types
- PEP 632 — Deprecate and remove distutils (took
strtoboolwith it) - What's New in Python 3.12 — distutils removal
- The Twelve-Factor App — Config
This article pairs with reel #105 of the Yeda AI Tips series. Building an AI-assisted engineering workflow? Yeda AI designs, audits, and ships production LLM systems.