← Back to .env.example Drift Check

.env.example Drift Check vs. grepping the codebase by hand

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.

CaseGrepping by handThis tool
A dot-notation read, process.env.API_KEYCorrectly caught by a grep written for the dot patternCorrectly 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 skippedMatched 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 serviceNeeds a second, correctly-written grep per language, run every time -- easy to run only the one for the file you are already looking atScans 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.exampleA manual set-difference held in your head -- the kind of exhaustive comparison people get wrong under time pressure, with no signal when they doA 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 anymoreNobody 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 monthA one-time audit answers for today; nothing re-runs it on the next commitcli.mjs wired into CI re-checks every PR that touches source or the example file

The gap that matters most: bracket notation has no shared substring with the dot pattern

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.

The gap that matters second: one grep per language, applied inconsistently

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.

When to still check the codebase yourself

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.

Common questions

What does grepping for process.env actually catch?

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.

Why does a grep for process.env miss bracket-notation reads?

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.

Why does a grep audit not scale past one language?

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.

Why is eyeballing grep hits against .env.example error-prone?

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.

Does this tool replace checking the codebase by hand entirely?

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.