Build1 distinct publisher3 min readUpdated
A generated feature-flag helper returned the right answer on a laptop and raised NameError in a clean Docker image. An AST walk over loaded-but-unbound names catches the whole class.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A developer writing on dev.to came within a commit of shipping a small feature-flag helper that returned the boolean he expected on his laptop and then crashed in the clean Docker image [1]. The generated function was quietly reading module-level names his interactive shell still held from an older script, so the container, which had none of them, raised NameError on the first call [3][2].
The artifact was short enough to look trustworthy. It defined `is_enabled(feature)`, returning `feature in FEATURE_STORE` when `DEBUG` was true and `feature in DEFAULT_FEATURES` otherwise [4]. Three names appear there that the function never binds: `DEBUG`, `FEATURE_STORE` and `DEFAULT_FEATURES`, none of them a parameter, a return value or an import [5]. The source does not label them as free globals, and the author's point is that a quick read of the body can miss them entirely [6].
The two runs make the asymmetry concrete. Executed in a namespace pre-seeded with `DEBUG` false, a `FEATURE_STORE` dict and a `DEFAULT_FEATURES` set, the call printed `False`, exactly as expected [7]. Executed in an empty namespace standing in for the container, the same source failed with `name 'DEBUG' is not defined` [8]. The author's reading is that only the second run told the truth: the first passed because his environment supplied the missing pieces, not because the code was complete [9].
Worth noticing how little the passing test actually exercised. With `DEBUG` bound to false, that single call evaluated `DEBUG` and `DEFAULT_FEATURES` and never touched `FEATURE_STORE` [19]. And NameError is raised one name at a time, which is why the clean run named `DEBUG` and stopped there [8]. A test loop that only runs the code in an empty namespace therefore needs up to three iterations, one crash each, to enumerate a three-name dependency set [20]. Dynamic execution reports the first thing missing on the path you happened to take; it does not report the contract.
Static inspection does. The recommended check is to find every name the generated code loads but neither assigns nor receives as a parameter, using an AST walk rather than a regex, because identifier scanning is too easily fooled by nested functions, comprehensions and string annotations [11][12]. The analyzer in the post seeds a set of bound names with `dir(builtins)`, adds every Store-context `Name`, every `ast.arg`, every function and class definition name, and every import alias, then returns the sorted difference between Load-context names and that set [13]. On the helper it prints `['DEFAULT_FEATURES', 'DEBUG', 'FEATURE_STORE']` [14]. That is all three undeclared names recovered in one pass, without executing anything [18]. The rule the author draws is the operationally useful part: a non-empty result means the artifact is not self-contained and should not be treated as finished until the caller either passes those names explicitly or marks them as an intentional runtime dependency [15].
Two things to watch if you wire this up. The analyzer proves nothing about behaviour, so the post pairs it with a second guard: execute the generated source in a namespace containing only builtins plus values you deliberately inject [16]. And the gate belongs in the code path that turns model output into an evaluation artifact, which the author describes as provider-neutral, since clean syntax and undeclared state dependencies coexist happily [10]. One caveat on provenance: the author discloses that the bug was captured while generating the helper with MonkeyCode's free model access, that the clean-namespace check was re-run on its free server option, and that the article was prepared as part of MonkeyCode's product outreach [17].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The author executed the snippet in a namespace seeded with DEBUG False, FEATURE_STORE {'dark': False} and DEFAULT_FEATURES {'dark','beta'}, and the call is_enabled('dark') printed False, matching his expectation.
Executing the same source in an empty namespace simulating a clean container failed with the message "name 'DEBUG' is not defined".
Running the analyzer on the generated helper prints ['DEFAULT_FEATURES', 'DEBUG', 'FEATURE_STORE'].
The author almost shipped a small feature-flag helper that worked on his laptop and crashed in the clean Docker image.
The clean container did not have the globals the generated function expected, so the same code raised NameError at the first call.
The generated function was silently reading module-level variables that the author's interactive shell still held from an older script.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Reproducible stdlib demo, single self-reported source
The mechanism is fully shown rather than asserted: the generated helper, the seeded-namespace pass, the empty-namespace NameError, and the complete free_globals AST analyzer are all printed and can be re-run by any reader with CPython alone, which is unusually strong for a single blog post. What holds the score down is that every fact traces to one dev.to article by one author, the production incident ('crashed in the clean Docker image') is narrated rather than evidenced with logs, the analyzer's coverage limits are untested, and the pasted output ordering conflicts with the sorted() call in the same code listing.
No adoption signal beyond the author's own workflow
Supplied material contains no deployment, download, repository, benchmark or third-party usage data for the free-variable check or for MonkeyCode. The only usage fact is the author's disclosed promotional use of MonkeyCode while producing the article, which does not evidence adoption of the technique or the tool by anyone else, so this dimension is left unmeasured rather than inferred.
Modest claims, lightly oversold framing
The technical claims are narrow and self-limiting: the author states the analyzer does not prove the function works, concedes not every free variable is a bug, and adds a second runtime guard. That is close to aligned with the evidence shown. The small positive gap comes from packaging rather than substance: the cluster framing that an AST walk 'catches the whole class' overreaches a check that only finds Load-context bare names and misses indirection, and the piece is disclosed vendor outreach generalising from one laptop-versus-container anecdote with no measured incidence.
Disclosed vendor product outreach
The article states outright that it was prepared as part of MonkeyCode's product outreach and that the vendor's free model access and free server option were used in the workflow, which is a direct commercial interest in the narrative. The disclosure is explicit and in-body, and the prescribed remedy is stdlib-only and provider-neutral rather than a product pitch, which mitigates but does not remove the incentive to frame generated-code failure modes around this vendor's tooling.
Verifiable technique, single incentivised source
Confidence is limited by the cluster shape: one publisher, one item, one author, an unverifiable production-crash narrative, disclosed vendor outreach, an internally inconsistent output transcript, and a body that appears truncated mid-sentence. It is not lower because the load-bearing artifact, the AST analyzer and the namespace experiments, can be checked by any reader in minutes without trusting the author, and no supplied evidence contradicts the mechanism.
build
Stop timing your GraphQL tests and start counting loader calls1 distinct publisher
build
Your 90% Cache Hit Ratio Is a Lagging Indicator. Alert on Cold Misses Per Key1 distinct publisher
build
Before you spend quota on an agent skill, make it pass an eval harness1 distinct publisher
build
An AI test suite hit 94% coverage and missed the one branch that mattered1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 18, 2026