CI installs templ@latest while go.mod pins v0.3.1020 — generated code can drift from the committed output #13

Closed
opened 2026-08-28 20:02:31 +00:00 by mathias · 1 comment
Owner

Problem

.gitea/workflows/cd.yml:49:

go install github.com/a-h/templ/cmd/templ@latest

go.mod:5:

require github.com/a-h/templ v0.3.1020

The generator is unpinned; the runtime library is pinned. CI therefore runs whatever templ released most recently against a library it may not match.

Why it matters

task check runs templ generate before linting and testing. The *_templ.go files are committed. So the moment upstream templ changes its codegen, CI regenerates different Go than what is in the repo — and depending on how the diff falls, that surfaces as a lint failure, a compile error against the pinned runtime, or silently different rendered HTML that no test covers.

Nothing fails today, which is the problem: the drift is invisible until an upstream release lands, and then it fails on an unrelated commit and looks like that commit's fault. This estate has already lost time to a templ version skew once.

Found while running task check locally: templ was absent on koala, and installing @latest rather than the pinned version would have silently reproduced the CI configuration rather than the repo's.

Proposed

Pin the generator to the library:

go install github.com/a-h/templ/cmd/templ@v0.3.1020

Better, so the two cannot drift apart again: add a tool directive (Go 1.24+) or a tools.go, and install with go install github.com/a-h/templ/cmd/templ so the version comes from go.mod itself and Dependabot-style bumps move both together.

Optionally add a CI guard: run templ generate then git diff --exit-code on *_templ.go, so regenerating in CI must be a no-op. That turns silent drift into an explicit failure at the commit that caused it.

Done when

  • The templ binary version used in CI is derived from go.mod, not @latest.
  • Regenerating in CI produces no diff against the committed *_templ.go.

Notes

Found while implementing #10, 2026-08-28. Same class as the pinned-vs-latest drift already recorded for this estate.

## Problem `.gitea/workflows/cd.yml:49`: ```yaml go install github.com/a-h/templ/cmd/templ@latest ``` `go.mod:5`: ``` require github.com/a-h/templ v0.3.1020 ``` The generator is unpinned; the runtime library is pinned. CI therefore runs whatever `templ` released most recently against a library it may not match. ## Why it matters `task check` runs `templ generate` before linting and testing. The `*_templ.go` files are **committed**. So the moment upstream `templ` changes its codegen, CI regenerates different Go than what is in the repo — and depending on how the diff falls, that surfaces as a lint failure, a compile error against the pinned runtime, or silently different rendered HTML that no test covers. Nothing fails today, which is the problem: the drift is invisible until an upstream release lands, and then it fails on an unrelated commit and looks like that commit's fault. This estate has already lost time to a templ version skew once. Found while running `task check` locally: `templ` was absent on koala, and installing `@latest` rather than the pinned version would have silently reproduced the CI configuration rather than the repo's. ## Proposed Pin the generator to the library: ```yaml go install github.com/a-h/templ/cmd/templ@v0.3.1020 ``` Better, so the two cannot drift apart again: add a `tool` directive (Go 1.24+) or a `tools.go`, and install with `go install github.com/a-h/templ/cmd/templ` so the version comes from `go.mod` itself and Dependabot-style bumps move both together. Optionally add a CI guard: run `templ generate` then `git diff --exit-code` on `*_templ.go`, so regenerating in CI must be a no-op. That turns silent drift into an explicit failure at the commit that caused it. ## Done when - The `templ` binary version used in CI is derived from `go.mod`, not `@latest`. - Regenerating in CI produces no diff against the committed `*_templ.go`. ## Notes Found while implementing #10, 2026-08-28. Same class as the pinned-vs-latest drift already recorded for this estate.
Author
Owner

Fixed by mathias/cad@8a0c76e as part of the cad-atlas merge.

The root cause was as described: templ generate ran at build time against a CLI installed with @latest, while the runtime came from go.mod. Worse, after the merge go mod tidy removed a-h/templ entirely — the only importers were generated files that are neither tracked nor present, so tidy saw no requirement, and the image build then failed on generated code importing a module go.mod no longer required.

Fixed at the cause rather than by pinning the CLI: the _templ.go files are now committed (matching cobalt-dingo's precedent), so go mod tidy keeps the runtime and the build needs neither a templ install nor network. Verified with buildctl --opt target=atlas-runtime.

Fixed by `mathias/cad@8a0c76e` as part of the cad-atlas merge. The root cause was as described: `templ generate` ran at build time against a CLI installed with `@latest`, while the runtime came from `go.mod`. Worse, after the merge `go mod tidy` *removed* `a-h/templ` entirely — the only importers were generated files that are neither tracked nor present, so tidy saw no requirement, and the image build then failed on generated code importing a module go.mod no longer required. Fixed at the cause rather than by pinning the CLI: the `_templ.go` files are now **committed** (matching cobalt-dingo's precedent), so `go mod tidy` keeps the runtime and the build needs neither a templ install nor network. Verified with `buildctl --opt target=atlas-runtime`.
This repo is archived. You cannot comment on issues.