← Back to .env.example Drift Check
grep -rn "process\.env\." src/ genuinely works for the case it is written for, and it is
the method most people already reach for. This page names no product or company being compared
against: grepping is a generic manual habit, not a competing tool.
| Case | Grepping by hand | This tool |
|---|---|---|
A dot-notation read, process.env.API_KEY | Correctly caught by a grep written for the dot pattern | Correctly caught the same way, as one of the JavaScript/TypeScript patterns |
A bracket-notation read, process.env["AWS_REGION"] | Invisible to a grep tuned for process\.env\. -- no match, no warning that anything was skipped | Matched by a separate bracket-notation pattern, checked every time regardless of which notation a file uses |
| A polyglot repo with a JavaScript frontend and a Python or Ruby service | Needs a second, correctly-written grep per language, run every time -- easy to run only the one for the file you are already looking at | Scans for all four languages' read patterns in the same pass, whether or not the repo mixes them |
Cross-referencing 40 grep hits against 15 names in .env.example | A manual set-difference held in your head -- the kind of exhaustive comparison people get wrong under time pressure, with no signal when they do | A deterministic Map diff: exactly the names read in code but absent from the example file, every time |
A variable declared in .env.example that nothing reads anymore | Nobody checks this direction unless someone thinks to -- it produces no crash, just silently stale docs | --strict flags every declared-but-unread name the same way, every run |
| Catching drift a PR introduces next month | A one-time audit answers for today; nothing re-runs it on the next commit | cli.mjs wired into CI re-checks every PR that touches source or the example file |
process.env["AWS_REGION"] and process.env.AWS_REGION read the identical
variable, but a grep pattern written to catch the second -- typically process\.env\.[A-Z_]+
or similar -- does not match the first at all. There is no partial hit, no warning line, nothing to
notice: the bracket-notation read simply does not appear in the grep's output, and a review that trusts
a clean grep pass has no way to tell the difference between "no missing variables" and "a whole
notation this grep can't see." This tool's scanSource matches both notations as separate,
always-checked patterns, so which style a given file happens to use does not change the result.
os.getenv("FOO"), os.environ["FOO"], ENV["FOO"], and
System.getenv("FOO") are four more call shapes across three more languages, each needing
its own grep pattern to catch. A repo that mixes a JavaScript frontend with a Python worker or a Ruby
background service needs someone to remember, and correctly write, a separate grep for each one --
and it is the service nobody is currently staring at that a quick audit skips. This tool checks all of
them in the same pass over whatever text it is handed, so the check does not depend on which language
the person running it happens to be thinking about.
This is a fixed set of text patterns, not code evaluation -- it cannot see a variable name assembled at
runtime (process.env[someVariable]), and it does not read shell scripts, YAML, or Docker
Compose files at all, so a variable read only through one of those is invisible to it the same as it
would be to a grep. Confirming a name outside its pattern list, and understanding a dynamically-built
variable name, is still a job for reading the code yourself.
grep -rn "process\.env\." src/ finds every dot-notation read in one pass and is genuinely fast for a single-language JavaScript codebase. That case is real and this tool's engine.mjs matches it the same way, as one of its LANGUAGE_PATTERNS entries.
process.env["AWS_REGION"] and process.env['AWS_REGION'] both read the variable, but neither contains the literal substring process.env. followed directly by a bare name -- the dot-then-identifier pattern most people type into grep. A grep tuned for the dot-notation case does not match the bracket case at all, and it never announces the miss; the line simply never appears in the results.
process.env, os.getenv, os.environ[], ENV[], and System.getenv are five different call shapes across four languages. A repo with a JavaScript frontend and a Python worker needs two separate, correctly-written greps run every time, and it is easy to run the one for the language you are staring at and skip the other -- especially in a service you did not personally write.
A real codebase's process.env reads run into the dozens once every file that touches configuration is counted, most of them already declared. The task is a manual set-difference: read down a list of hits, hold the .env.example file's declared names in your head, and notice the one name that is not on both sides. That kind of exhaustive comparison against a mental list is exactly the class of check people get wrong under time pressure, and a clean-looking grep pass gives no signal about whether it was done right.
No. It scans for a fixed list of call-shape patterns in text and knows nothing about shell scripts, YAML, or Docker Compose files reading a variable through some other mechanism, and it never evaluates code -- a name built dynamically at runtime (process.env[someVariable]) is invisible to it the same as it would be to a grep. Confirming a variable name it did not catch, or one read through a mechanism outside its pattern list, is still a job for reading the code yourself.