Owner ruling, sneak 2026-08-09: every lint run happens inside a Docker container, invoked through the script/ entrypoint. Docker is always available. Linting runs independently and does not need a cache. He has directed a PR for every repo not already set up this way.
Reference implementation is sneak/homoicon — copy its shape: a root Dockerfile.lint built FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240, which COPYs the repo in and runs golangci-lint run --config .golangci.yml ./... as a build step, with script/lint reduced to building it. Linting as a build step means a successful build IS a clean lint.
This repo is polyglot, so the ruling covers the frontend linter too — the same containerised pattern around whatever lints the SPA, not only the Go side. Sequence it against the in-flight gate work rather than conflicting with it.
Two things to get right, both of which would otherwise ship a false green:
A cached build lints nothing. A lint build on an unchanged tree returns success in well under a second having run no linter. Caching is explicitly waived here, so force the lint layers to execute.
golangci-lint config verify fetches its JSON schema over an unpinned live HTTPS call — this repo already identified that hazard. Decide deliberately whether to include it.
Also remove golangci-lint installation from script/bootstrap — nothing runs on the host any more.
Definition of done
script/lint runs every linter only in Docker; no host path remains.
Two consecutive script/lint runs on an unchanged tree both demonstrably execute the linters.
Negative control: introduce a deliberate lint violation, confirm it fails with that specific finding, revert, confirm clean.
Owner ruling, sneak 2026-08-09: every lint run happens inside a Docker container, invoked through the `script/` entrypoint. Docker is always available. Linting runs independently and does not need a cache. He has directed a PR for every repo not already set up this way.
Reference implementation is `sneak/homoicon` — copy its shape: a root `Dockerfile.lint` built `FROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240`, which COPYs the repo in and runs `golangci-lint run --config .golangci.yml ./...` as a build step, with `script/lint` reduced to building it. Linting as a build step means a successful build IS a clean lint.
This repo is polyglot, so the ruling covers the frontend linter too — the same containerised pattern around whatever lints the SPA, not only the Go side. Sequence it against the in-flight gate work rather than conflicting with it.
Two things to get right, both of which would otherwise ship a false green:
1. **A cached build lints nothing.** A lint build on an unchanged tree returns success in well under a second having run no linter. Caching is explicitly waived here, so force the lint layers to execute.
2. **`golangci-lint config verify` fetches its JSON schema over an unpinned live HTTPS call** — this repo already identified that hazard. Decide deliberately whether to include it.
Also remove golangci-lint installation from `script/bootstrap` — nothing runs on the host any more.
## Definition of done
- `script/lint` runs every linter only in Docker; no host path remains.
- Two consecutive `script/lint` runs on an unchanged tree both demonstrably execute the linters.
- Negative control: introduce a deliberate lint violation, confirm it fails with that specific finding, revert, confirm clean.
- `make check` still green.
Canonical tracking issue: https://git.eeqj.de/sneak/prompts/issues/40
Plan. sneak's later ruling on the canonical issue settles the shape (sneak/prompts#40 (comment), and "fmt and fmt check arent docker, just linting"): linting is its own stage in the main Dockerfile, which script/lint builds alone with no cache, with a dependency that keeps the image build from finishing unless it passed; no Dockerfile.lint; no golangci-lint config verify; formatting and its check stay on the host.
The Go half already has that shape. What is left is the frontend: its "linter" today is prettier --check, the same check script/fmt-check runs, so the repo has no JavaScript linter at all. This unit adds one and takes item 2 of #28 with it:
eslint as a dev dependency, with a flat config (eslint.config.js) holding its recommended rules and nothing invented; existing findings fixed, or a rule switched off with a one-line reason in the config.
A frontend lint stage in Dockerfile, from the pinned node image, that installs from the lockfile and runs eslint, with the same kind of dependency the Go lint stage has so the final image cannot build unless it passed.
script/lint builds both lint stages with --no-cache; script/frontend-lint runs eslint and no longer runs prettier. script/fmt-check keeps prettier on the host. The node stage's make frontend-check runs no docker build inside the image build.
Proof in the PR (not on the tracker): two runs on an unchanged tree both run eslint; a planted lint violation fails naming it, and passes once reverted.
README Entrypoints and TODO.md updated.
Adding the dependency needs a raw yarn add, since no entrypoint does that yet (#45); disclose it in one line and leave yarn.lock fully hash-pinned.
Model: opus-5-5
Plan. sneak's later ruling on the canonical issue settles the shape (https://git.eeqj.de/sneak/prompts/issues/40#issuecomment-54891, and "fmt and fmt check arent docker, just linting"): linting is its own stage in the main `Dockerfile`, which `script/lint` builds alone with no cache, with a dependency that keeps the image build from finishing unless it passed; no `Dockerfile.lint`; no `golangci-lint config verify`; formatting and its check stay on the host.
The Go half already has that shape. What is left is the frontend: its "linter" today is `prettier --check`, the same check `script/fmt-check` runs, so the repo has no JavaScript linter at all. This unit adds one and takes item 2 of https://git.eeqj.de/sneak/netwatch/issues/28 with it:
1. `eslint` as a dev dependency, with a flat config (`eslint.config.js`) holding its recommended rules and nothing invented; existing findings fixed, or a rule switched off with a one-line reason in the config.
2. A frontend lint stage in `Dockerfile`, from the pinned node image, that installs from the lockfile and runs eslint, with the same kind of dependency the Go lint stage has so the final image cannot build unless it passed.
3. `script/lint` builds both lint stages with `--no-cache`; `script/frontend-lint` runs eslint and no longer runs prettier. `script/fmt-check` keeps prettier on the host. The node stage's `make frontend-check` runs no docker build inside the image build.
4. Proof in the PR (not on the tracker): two runs on an unchanged tree both run eslint; a planted lint violation fails naming it, and passes once reverted.
5. README Entrypoints and `TODO.md` updated.
Adding the dependency needs a raw `yarn add`, since no entrypoint does that yet (https://git.eeqj.de/sneak/netwatch/issues/45); disclose it in one line and leave `yarn.lock` fully hash-pinned.
Model: opus-5-5
Built in #97: eslint with its recommended rules runs in its own frontend-lint stage of Dockerfile, which gates the frontend stage the way the Go lint stage gates the builder. script/lint builds both lint stages without the cache, and prettier stays on the host in script/fmt-check. The same PR covers item 2 of #28.
Model: opus-5-5
Built in https://git.eeqj.de/sneak/netwatch/pulls/97: `eslint` with its recommended rules runs in its own `frontend-lint` stage of `Dockerfile`, which gates the frontend stage the way the Go `lint` stage gates the builder. `script/lint` builds both lint stages without the cache, and prettier stays on the host in `script/fmt-check`. The same PR covers item 2 of https://git.eeqj.de/sneak/netwatch/issues/28.
Model: opus-5-5
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.
Owner ruling, sneak 2026-08-09: every lint run happens inside a Docker container, invoked through the
script/entrypoint. Docker is always available. Linting runs independently and does not need a cache. He has directed a PR for every repo not already set up this way.Reference implementation is
sneak/homoicon— copy its shape: a rootDockerfile.lintbuiltFROM golangci/golangci-lint:v2.12.2@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240, which COPYs the repo in and runsgolangci-lint run --config .golangci.yml ./...as a build step, withscript/lintreduced to building it. Linting as a build step means a successful build IS a clean lint.This repo is polyglot, so the ruling covers the frontend linter too — the same containerised pattern around whatever lints the SPA, not only the Go side. Sequence it against the in-flight gate work rather than conflicting with it.
Two things to get right, both of which would otherwise ship a false green:
golangci-lint config verifyfetches its JSON schema over an unpinned live HTTPS call — this repo already identified that hazard. Decide deliberately whether to include it.Also remove golangci-lint installation from
script/bootstrap— nothing runs on the host any more.Definition of done
script/lintruns every linter only in Docker; no host path remains.script/lintruns on an unchanged tree both demonstrably execute the linters.make checkstill green.Canonical tracking issue: sneak/prompts#40
Plan. sneak's later ruling on the canonical issue settles the shape (sneak/prompts#40 (comment), and "fmt and fmt check arent docker, just linting"): linting is its own stage in the main
Dockerfile, whichscript/lintbuilds alone with no cache, with a dependency that keeps the image build from finishing unless it passed; noDockerfile.lint; nogolangci-lint config verify; formatting and its check stay on the host.The Go half already has that shape. What is left is the frontend: its "linter" today is
prettier --check, the same checkscript/fmt-checkruns, so the repo has no JavaScript linter at all. This unit adds one and takes item 2 of #28 with it:eslintas a dev dependency, with a flat config (eslint.config.js) holding its recommended rules and nothing invented; existing findings fixed, or a rule switched off with a one-line reason in the config.Dockerfile, from the pinned node image, that installs from the lockfile and runs eslint, with the same kind of dependency the Go lint stage has so the final image cannot build unless it passed.script/lintbuilds both lint stages with--no-cache;script/frontend-lintruns eslint and no longer runs prettier.script/fmt-checkkeeps prettier on the host. The node stage'smake frontend-checkruns no docker build inside the image build.TODO.mdupdated.Adding the dependency needs a raw
yarn add, since no entrypoint does that yet (#45); disclose it in one line and leaveyarn.lockfully hash-pinned.Model: opus-5-5
Built in #97:
eslintwith its recommended rules runs in its ownfrontend-lintstage ofDockerfile, which gates the frontend stage the way the Golintstage gates the builder.script/lintbuilds both lint stages without the cache, and prettier stays on the host inscript/fmt-check. The same PR covers item 2 of #28.Model: opus-5-5