Two latent bugs surfaced building a real project (mathias/cad-atlas) from this template, 2026-07-19. Both bite every child repo on first build.
1. templ version skew (build-breaking)
go.mod pins github.com/a-h/templ v0.2.778, but CI installs templ@latest (currently v0.3.x). The v0.3 generator emits templruntime.WriteString, which does not exist in the v0.2.778 runtime:
So task check / go build fails on a freshly generated repo.
Fix: pin the generator and runtime to the same minor. Either bump go.mod to the current templ (go get -u github.com/a-h/templ@latest + regenerate) and pin CI to a matching templ@vX.Y.Z, or hold both at v0.2.778. Don't let CI float to @latest against a pinned runtime.
2. .gitignore pattern doesn't match templ output
Pattern is *.templ.go (dot). templ actually emits *_templ.go (underscore). Generated files are therefore not ignored and leak into git add -A.
Fix: change the pattern to *_templ.go.
Repro
# create from template, then:
go install github.com/a-h/templ/cmd/templ@latest
templ generate && go build ./... # → undefined: templruntime.WriteString
git add -A && git status # → index_templ.go / layout_templ.go staged (should be ignored)
Related brain note: wiki/homelab/decisions/cad-atlas-audit-chain-is-viz-data.md (template-go-web footguns section). Also note the known no-go.sum-shipped behavior (child runs go mod tidy).
Two latent bugs surfaced building a real project (`mathias/cad-atlas`) from this template, 2026-07-19. Both bite every child repo on first build.
## 1. templ version skew (build-breaking)
`go.mod` pins `github.com/a-h/templ v0.2.778`, but CI installs `templ@latest` (currently v0.3.x). The v0.3 generator emits `templruntime.WriteString`, which does not exist in the v0.2.778 runtime:
```
internal/web/index_templ.go:44:38: undefined: templruntime.WriteString
```
So `task check` / `go build` fails on a freshly generated repo.
**Fix:** pin the generator and runtime to the same minor. Either bump `go.mod` to the current templ (`go get -u github.com/a-h/templ@latest` + regenerate) and pin CI to a matching `templ@vX.Y.Z`, or hold both at v0.2.778. Don't let CI float to `@latest` against a pinned runtime.
## 2. `.gitignore` pattern doesn't match templ output
Pattern is `*.templ.go` (dot). templ actually emits `*_templ.go` (underscore). Generated files are therefore **not** ignored and leak into `git add -A`.
**Fix:** change the pattern to `*_templ.go`.
## Repro
```bash
# create from template, then:
go install github.com/a-h/templ/cmd/templ@latest
templ generate && go build ./... # → undefined: templruntime.WriteString
git add -A && git status # → index_templ.go / layout_templ.go staged (should be ignored)
```
Related brain note: `wiki/homelab/decisions/cad-atlas-audit-chain-is-viz-data.md` (template-go-web footguns section). Also note the known no-`go.sum`-shipped behavior (child runs `go mod tidy`).
Gate review of PR #13 — not merged, blocking finding is unrelated to this issue. Full evidence: #13 (comment 11976).
Both defects reported here are fixed and, more importantly, both are now regression-tested:
templ version skew — go.mod moves to v0.3.1020, and CI no longer installs @latest; it derives the generator version from go.mod. Confirmed in the run 7511 log: templ library is v0.3.1020 — installing the matching generator. Reintroducing the skew (go.mod back to v0.2.778) turns scripts/tests/generated-child_test.sh red: 1 passed, 4 failed.
.gitignore pattern — *.templ.go → *_templ.go. Verified by running templ generate and checking git status --porcelain is empty; generated files are now ignored by rule rather than by luck.
On the note in the body that the missing go.sum was known behavior with the child running go mod tidy — PR #13 takes the other road and ships a go.sum. That is the better call: without it go build ./... fails on a clean checkout, which is what was hiding the skew underneath. Deleting go.sum now also fails the harness (1 passed, 4 failed), so it cannot silently regress.
This issue's content is fully addressed. The PR is held only on a defect in the new harness (it grades a crashed golangci-lint as a pass — see #12 and #13), not on anything reported here.
**Gate review of PR #13 — not merged, blocking finding is unrelated to this issue.** Full evidence: #13 (comment 11976).
Both defects reported here are fixed and, more importantly, both are now regression-tested:
- **templ version skew** — `go.mod` moves to `v0.3.1020`, and CI no longer installs `@latest`; it derives the generator version from `go.mod`. Confirmed in the run 7511 log: `templ library is v0.3.1020 — installing the matching generator`. Reintroducing the skew (`go.mod` back to `v0.2.778`) turns `scripts/tests/generated-child_test.sh` red: `1 passed, 4 failed`.
- **`.gitignore` pattern** — `*.templ.go` → `*_templ.go`. Verified by running `templ generate` and checking `git status --porcelain` is empty; generated files are now ignored by rule rather than by luck.
On the note in the body that the missing `go.sum` was *known* behavior with the child running `go mod tidy` — PR #13 takes the other road and ships a `go.sum`. That is the better call: without it `go build ./...` fails on a clean checkout, which is what was hiding the skew underneath. Deleting `go.sum` now also fails the harness (`1 passed, 4 failed`), so it cannot silently regress.
This issue's content is fully addressed. The PR is held only on a defect in the *new* harness (it grades a crashed `golangci-lint` as a pass — see #12 and #13), not on anything reported here.
Closed by #13 (merged as rebase; main is now 2c4158a). Verified by running each fix and each regression, not by reading the diff.
1. templ version skew — fixed and regression-tested
go.mod moves to v0.3.1020, and CI no longer floats the generator. The install step now derives the generator version from the library:
templ_version="$(go list -m -f '{{.Version}}' github.com/a-h/templ)"
go install "github.com/a-h/templ/cmd/templ@$templ_version"
Confirmed in the run 7514 log: templ library is v0.3.1020 — installing the matching generator. There is no remaining @latest for templ anywhere in the workflow, so generator and library cannot drift apart again.
Reintroducing the skew (go.mod back to v0.2.778) now turns the gate red — 1 passed, 4 failed, exit 1 — instead of shipping to a child.
2. .gitignore pattern — fixed and verified
*.templ.go → *_templ.go. Verified by actually running templ generate and checking that the generated files are ignored:
$ templ generate && git status --porcelain
# empty — index_templ.go / layout_templ.go now ignored by rule
On the "known no-go.sum" note in the body
#13 takes the other road and ships a go.sum rather than relying on the child running go mod tidy. I think that is the right call: without it, go build ./... fails on a clean checkout, and that failure was what hid the templ skew underneath it — nothing downstream ever ran. Deleting go.sum now also fails the harness (1 passed, 4 failed), so the previous state cannot silently return.
Why this sat here with CI green
Both defects were reachable only from a child repo because the guard gated the check job as well as build/deploy, and is_template is always true here — so this template never ran its own gate. That is #12, fixed in the same PR: check is now ungated (run 7514: Lint / Test / Vet → success, Build & Import and Deploy via GitOps → skipped), and a new scripts/tests/generated-child_test.sh covers the entrypoint that ./... cannot see, since go list ./... on the raw template reports only internal/web.
Both defects reported here are fixed, and both now have a test that fails if they come back. The PR was held for one round on a defect in the new harness (it graded a crashed golangci-lint as a pass); corrected in 2c4158a and verified with exit-code/output-decoupled shims before merge — detail on #12.
**Closed by #13** (merged as rebase; `main` is now `2c4158a`). Verified by running each fix and each regression, not by reading the diff.
### 1. templ version skew — fixed and regression-tested
`go.mod` moves to `v0.3.1020`, and CI no longer floats the generator. The install step now derives the generator version from the library:
```
templ_version="$(go list -m -f '{{.Version}}' github.com/a-h/templ)"
go install "github.com/a-h/templ/cmd/templ@$templ_version"
```
Confirmed in the run 7514 log: `templ library is v0.3.1020 — installing the matching generator`. There is no remaining `@latest` for templ anywhere in the workflow, so generator and library cannot drift apart again.
Reintroducing the skew (`go.mod` back to `v0.2.778`) now turns the gate red — `1 passed, 4 failed`, exit 1 — instead of shipping to a child.
### 2. `.gitignore` pattern — fixed and verified
`*.templ.go` → `*_templ.go`. Verified by actually running `templ generate` and checking that the generated files are ignored:
```
$ templ generate && git status --porcelain
# empty — index_templ.go / layout_templ.go now ignored by rule
```
### On the "known no-`go.sum`" note in the body
#13 takes the other road and ships a `go.sum` rather than relying on the child running `go mod tidy`. I think that is the right call: without it, `go build ./...` fails on a clean checkout, and that failure was what hid the templ skew underneath it — nothing downstream ever ran. Deleting `go.sum` now also fails the harness (`1 passed, 4 failed`), so the previous state cannot silently return.
### Why this sat here with CI green
Both defects were reachable only from a child repo because the guard gated the `check` job as well as `build`/`deploy`, and `is_template` is always true here — so this template never ran its own gate. That is #12, fixed in the same PR: `check` is now ungated (run 7514: `Lint / Test / Vet` → success, `Build & Import` and `Deploy via GitOps` → skipped), and a new `scripts/tests/generated-child_test.sh` covers the entrypoint that `./...` cannot see, since `go list ./...` on the raw template reports only `internal/web`.
Both defects reported here are fixed, and both now have a test that fails if they come back. The PR was held for one round on a defect in the *new* harness (it graded a crashed `golangci-lint` as a pass); corrected in `2c4158a` and verified with exit-code/output-decoupled shims before merge — detail on #12.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two latent bugs surfaced building a real project (
mathias/cad-atlas) from this template, 2026-07-19. Both bite every child repo on first build.1. templ version skew (build-breaking)
go.modpinsgithub.com/a-h/templ v0.2.778, but CI installstempl@latest(currently v0.3.x). The v0.3 generator emitstemplruntime.WriteString, which does not exist in the v0.2.778 runtime:So
task check/go buildfails on a freshly generated repo.Fix: pin the generator and runtime to the same minor. Either bump
go.modto the current templ (go get -u github.com/a-h/templ@latest+ regenerate) and pin CI to a matchingtempl@vX.Y.Z, or hold both at v0.2.778. Don't let CI float to@latestagainst a pinned runtime.2.
.gitignorepattern doesn't match templ outputPattern is
*.templ.go(dot). templ actually emits*_templ.go(underscore). Generated files are therefore not ignored and leak intogit add -A.Fix: change the pattern to
*_templ.go.Repro
Related brain note:
wiki/homelab/decisions/cad-atlas-audit-chain-is-viz-data.md(template-go-web footguns section). Also note the known no-go.sum-shipped behavior (child runsgo mod tidy).Gate review of PR #13 — not merged, blocking finding is unrelated to this issue. Full evidence: #13 (comment 11976).
Both defects reported here are fixed and, more importantly, both are now regression-tested:
go.modmoves tov0.3.1020, and CI no longer installs@latest; it derives the generator version fromgo.mod. Confirmed in the run 7511 log:templ library is v0.3.1020 — installing the matching generator. Reintroducing the skew (go.modback tov0.2.778) turnsscripts/tests/generated-child_test.shred:1 passed, 4 failed..gitignorepattern —*.templ.go→*_templ.go. Verified by runningtempl generateand checkinggit status --porcelainis empty; generated files are now ignored by rule rather than by luck.On the note in the body that the missing
go.sumwas known behavior with the child runninggo mod tidy— PR #13 takes the other road and ships ago.sum. That is the better call: without itgo build ./...fails on a clean checkout, which is what was hiding the skew underneath. Deletinggo.sumnow also fails the harness (1 passed, 4 failed), so it cannot silently regress.This issue's content is fully addressed. The PR is held only on a defect in the new harness (it grades a crashed
golangci-lintas a pass — see #12 and #13), not on anything reported here.Closed by #13 (merged as rebase;
mainis now2c4158a). Verified by running each fix and each regression, not by reading the diff.1. templ version skew — fixed and regression-tested
go.modmoves tov0.3.1020, and CI no longer floats the generator. The install step now derives the generator version from the library:Confirmed in the run 7514 log:
templ library is v0.3.1020 — installing the matching generator. There is no remaining@latestfor templ anywhere in the workflow, so generator and library cannot drift apart again.Reintroducing the skew (
go.modback tov0.2.778) now turns the gate red —1 passed, 4 failed, exit 1 — instead of shipping to a child.2.
.gitignorepattern — fixed and verified*.templ.go→*_templ.go. Verified by actually runningtempl generateand checking that the generated files are ignored:On the "known no-
go.sum" note in the body#13 takes the other road and ships a
go.sumrather than relying on the child runninggo mod tidy. I think that is the right call: without it,go build ./...fails on a clean checkout, and that failure was what hid the templ skew underneath it — nothing downstream ever ran. Deletinggo.sumnow also fails the harness (1 passed, 4 failed), so the previous state cannot silently return.Why this sat here with CI green
Both defects were reachable only from a child repo because the guard gated the
checkjob as well asbuild/deploy, andis_templateis always true here — so this template never ran its own gate. That is #12, fixed in the same PR:checkis now ungated (run 7514:Lint / Test / Vet→ success,Build & ImportandDeploy via GitOps→ skipped), and a newscripts/tests/generated-child_test.shcovers the entrypoint that./...cannot see, sincego list ./...on the raw template reports onlyinternal/web.Both defects reported here are fixed, and both now have a test that fails if they come back. The PR was held for one round on a defect in the new harness (it graded a crashed
golangci-lintas a pass); corrected in2c4158aand verified with exit-code/output-decoupled shims before merge — detail on #12.