Skip to content

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

Illustration accompanying A --check run of the env generator exits non-zero when the committed catalog drifts

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.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories