Build1 publisher3 min readPublished
A --check run of the env generator exits non-zero when the committed catalog drifts
One Go service reads 59 environment variables, three quarters of them arriving with a platform library nobody on the service wrote, so the catalog is generated from the declaration and diffed in CI on every .go change.
The Engineer · Build desk

What happened
- Anton Brilliantov, writing on dev.to, described one Go service being carved out of a live PHP monolith whose single repository reads 59 environment variables.
- Three quarters of those variables arrived with the platform library, so nobody working on the service authored them or controls the list.
- The catalog is a generated YAML file committed to the repository, opening with a DO NOT EDIT header, a schema_version of 1 and a generation timestamp of 16 August 2026.
- Platform records name the platform package that owns the declaration, while service records name the file and line, such as internal/daemon/worker/retention/config.go:16.
- The same generator runs in CI on four triggers: any .go file, the service manifest, the modules file, and the snapshot itself.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A service team that authored only about a quarter of its variables cannot keep a hand-written list accurate, so the writable source of truth moves from the README into the code the generator reads.
- cost Every pull request that touches a .go file now owes a regenerate-and-commit step, and someone has to own the generator when the platform library changes shape.
- capability Deletion and ownership questions become a click into a declaration line instead of a repository-wide grep at the least convenient time.
- exposure Config-dumping tools get a machine-readable list of the variables that must never reach a log. A prose rule never gave them one.
The check does not validate the committed YAML against a schema. It runs the generator a second time in `--check` mode, holds the result in memory, compares it with the file on disk, and exits non-zero on any difference [5]. The failing case is an ordinary pull request: someone adds a field to a config struct, the committed catalog still describes the old set, and the job stops the merge. "A generated file that nobody regenerates is just a stale file with better formatting," Anton Brilliantov wrote [9].
The count justifies the tooling. Fifty-nine variables in one service, one repository [1]. Three quarters came in with the platform library [2], which leaves roughly 44 variables the service inherits against about 15 it declares itself [14]. Brilliantov puts the failure of the hand-written version this way: "At fifty-nine, hand-written documentation survives exactly until the next pull request - someone adds a field to a config struct, nobody touches the README, and from that moment the README is confidently wrong" [10].
Six fields per record, and the post stops at six [7]. `schema_version: 1` sits at the top because the file is read by machines as well as people, so consumers have something to branch on when the record shape changes [8]. The `secret` flag is there for the same audience: tools that dump configuration can read which variables must never appear in a log [15]. According to Brilliantov, the `defined_in` line closes questions; without it, "do we still need this one?" turns into a search across the repository run by whoever is unlucky [16].
The catalog is the file that falls out of the service manifest covered in part one of the series, one declaration the runtime reads instead of a pile of wiring code [13]. Brilliantov is a software engineer working mostly in PHP/Symfony and Go, carving a live PHP monolith into Go services [12].
He is explicit about scope. "As always: this is what I'm doing on one codebase right now, with the price attached. Not a recommendation for yours," he wrote [11]. The published post does not give a CI time for the extra generator run, and it does not estimate the work of keeping the generator in step with the platform library [17].
Two conditions have to hold for this to transfer. Variables have to be declared somewhere a generator can enumerate; `internal/daemon/worker/retention/config.go:16` implies that is how the service-side records are found [4]. And the platform library has to ship its own catalog, or the three quarters cannot be generated at all [2]. A service that reads configuration through bare `os.Getenv` calls scattered across handlers gives the generator nothing to walk, and the four triggers [6] then fire a job that can only confirm a partial list.
What to watch
- Whether Brilliantov publishes the generator itself, and the CI time the extra --check run adds to a pull request.
- Whether the platform library ships its own catalog for other services to import, which would make the inherited three quarters generatable.
- Whether schema_version moves past 1, and which machine consumers were reading the file when it did.