All checks were successful
check / check (push) Successful in 12s
script/lint runs the linter directly when it is already inside a container and otherwise builds Dockerfile.lint, so the linter never runs on a developer host. That closes three host-only mechanisms: the result cache golangci-lint keys on file content rather than location, which produced a confirmed false green and findings reported against other checkouts; the host-global $TMPDIR/golangci-lint.lock, which fails a run in a way no caller can distinguish from findings; and host/container version skew, which hid thirteen findings on one repo. Detection is on LINT_IN_CONTAINER=1, set by every Dockerfile, and on nothing else. The two directions are not symmetric: a false negative inside a container attempts a nested docker build, finds no daemon and fails loudly, while a false positive on a host silently lints there, which is the defect this issue exists to kill. /.dockerenv is therefore rejected even as a fallback -- measured absent inside BuildKit RUN steps and present on any host that is itself a container, so it fails in both directions and one of them is the dangerous one. Nothing else changes shape. The Dockerfile still runs make check, script/check still runs test, lint and fmt-check, script/cibuild is still a single docker build with CHECK_EPOCH and VERSION, and the Go multistage lint stage and its COPY --from=lint ordering dependency survive with ENV LINT_IN_CONTAINER=1 added. Dockerfile.lint is the standalone developer-host path and carries the same CHECK_EPOCH guard, with the ARG below the dependency layer so only the lint re-runs. The script/bootstrap golangci-lint install and the per-checkout GOLANGCI_LINT_CACHE/TMPDIR wrapper are deleted as superseded. Neither has a caller left. A JS repo's yarn install stays: the rule is that no lint verdict may come from a host invocation, not that no linter binary may exist there, and in a repo whose formatter is its linter the formatter necessarily runs on the host. golangci-lint config verify is kept, on measurement. Under the pinned v2.12.2 a bogus top-level key and a bogus key under linters.settings.lll both pass `golangci-lint run` with exit 0 and `0 issues` while config verify exits 3 and names them; an unknown linter name fails run and passes config verify. It needs no network: every case reproduced byte-identically under `docker run --network none`, in a container where `getent hosts golangci-lint.run` exits 2. Comment blocks were cut hard across every file this unit touches. .dockerignore drops from 67 comment lines to 28, script/cibuild from 17 to 12, script/docker from 18 to 12, and prompts/REPO_POLICIES.md from 1182 lines to 907. What remains says why a line is load-bearing; the discovery narratives are gone. config verify lives in script/lint's native branch rather than in a Dockerfile, so every path that lints inherits it: the lint stage of the main image, which is what CI runs, as well as Dockerfile.lint. Putting it in one Dockerfile is how the other path silently loses it. Model: opus-5
10 KiB
10 KiB
title, last_modified
| title | last_modified |
|---|---|
| Existing Repo Checklist | 2026-08-10 |
Use this checklist when beginning work in a repo that may not yet conform to our
repository policies
(https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md).
Work on a feature branch. Check each item and fix any gaps before proceeding with your task.
Formatting (do this first)
- If the repo has never been formatted to our standards, run
make fmtand commit the result as a standalone branch/commit/PR before any other changes. Formatting diffs can be large and should not be mixed with functional changes.
Required Files
README.mdexists with all required sections (Description, Getting Started, Rationale, Design, TODO, License, Author)LICENSEfile exists and matches the READMEREPO_POLICIES.mdexists and version date is current — fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md.gitignoreis comprehensive (OS, editor, agent scratch, language artifacts, secrets) — fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignoreif missing. An existing repo usually has a hand-written one that is never re-fetched, so check the entries rather than the file's presence:.claude/in particular, unanchored, so agent worktrees cannot be committed by accident. Do not give it a**/prefix — that is a.dockerignoreform and is wrong here..editorconfigexists — fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.editorconfigDockerfileand.dockerignoreexist (fetch.dockerignorefromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.dockerignore); Dockerfile runsmake checkas a build step, and every stage containing a check-runningRUNdeclaresARG CHECK_EPOCHwith theRUN [ -n "$CHECK_EPOCH" ] || exit 1guard immediately below it — see theCHECK_EPOCHrule inREPO_POLICIES.md. Without them the check layer is served from cache on an unchanged tree and the build reports a green it never ran.- Every stage that runs checks sets
ENV LINT_IN_CONTAINER=1— the lint stage and the build stage both. This is the item an existing repo most often fails after adopting the containerised lint: without itscript/linttries to buildDockerfile.lintfrom inside a build step, where there is no daemon. Dockerfile.lintexists andscript/lintbuilds it when not already in a container — see the containerised-lint rule inREPO_POLICIES.md. Base image pinned by sha256 with a version/date comment,ARG CHECK_EPOCHafter the dependency layer with the guard below it..dockerignoreexcludes the repo's own host-built artifacts (compiled binaries, test binaries, coverage output), written root-anchored —/myapp, never**/myapp, which would also matchcmd/myapp/. An existing repo is where such a binary is likeliest to already be sitting in the build context, invisible to git..dockerignoreexcludes.claude, root-anchored and with no**/prefix. Agent worktrees are entire checkouts of the repo, so they inflate the context by a multiple of it and can copy another session's unreviewed work into an image layer. Confirm by enumerating the image, not by reading the file —.gitignorehides these fromgit statustoo.- Do agents in this repo run anywhere other than the repo root? The
scratch directory is created in the agent's working directory, so the
canonical anchored entry misses
services/api/.claude/in a monorepo with a per-service agent — it still reaches the build context and the image. An existing repo is where such a layout already exists, so check it here rather than assuming the canonical entry covers you: add anchored entries for the subdirectories that have one (/services/api/.claude), or**/.claudeonce you have confirmed no legitimately named nested directory would be caught. - If the repo embeds a version in a binary, that version is computed on the
host and passed with
--build-arg VERSION=...byscript/dockerandscript/cibuild. No stage callsgit describe:.dockerignoreexcludes.git, so it yields an empty version without failing the build. A tag-derived version additionally needsfetch-depth: 0on the CI checkout step, which clones shallow and fetches no tags by default. - Every depth-independent pattern in
.dockerignorecarries a**/prefix; only genuinely root-anchored entries such as.gitare unprefixed, and.gitignore's patterns have not been transplanted unmodified..dockerignoreanchors an unprefixed pattern at the context root, so the transplanted form leavesconfig/.envandcerts/server.keyin the build context while reading as solved — see the.dockerignorerule inREPO_POLICIES.md. - Gitea Actions workflow in
.gitea/workflows/runsscript/cibuildon push — referencehttps://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml - Language-specific config:
- Go:
go.mod,go.sum,.golangci.yml(fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml) - JS:
package.json,yarn.lock,.prettierrc,.prettierignore(fetch fromhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierrcandhttps://git.eeqj.de/sneak/prompts/raw/branch/main/.prettierignore) - Python:
pyproject.toml - Docs/writing:
.prettierrc,.prettierignore(same URLs as above)
- Go:
Makefile and script/ Entrypoints
Makefileexists in root — referencehttps://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile- Has targets:
test,lint,fmt,fmt-check,check,docker,hooks - Target implementations live in
script/(scripts-to-rule-them-all); Makefile targets are thin shims calling them — model scripts athttps://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name> script/precommitexists and the pre-commit hook (installed byscript/install-precommit, shimmed bymake hooks) runs it- README has an Entrypoints section documenting the
script/entrypoints and linking the standard script/lintis the canonical detect-and-branch form, and no host invocation anywhere in the repo can produce a lint verdict — grep for the linter's own name acrossscript/, theMakefileand CI config, not justscript/lint. A second path is likeliest here: amake lint-fast, an older container-versus-host branch, or a CI step calling the binary directly. Expected hits that are not the defect:script/fmt, and in a repo whose formatter is also its linter,script/fmt-check. Everything else the grep finds is a real second path and goes.- Detection is on
LINT_IN_CONTAINERalone. Reject any/.dockerenvor cgroup heuristic: absent in BuildKitRUNsteps, present on hosts that are themselves containers, and a false positive lints on the host. script/bootstrapinstalls no golangci-lint. Delete the block, its version and ref variables, and its call site. A JS repo'syarn installstays — it brings a linter along with every other dependency, which is fine as long as no verdict is taken from it.- The per-checkout lint state is gone: no
GOLANGCI_LINT_CACHEorTMPDIRexports, no--allow-serial-runners, and.lint-cache/removed from.gitignoreand.dockerignore. make checkdoes not modify any files in the repomake testhas a 90-second timeout and completes within the 60-second hard cap (over 20 seconds is green but must be filed as an improvement bug)make testruns real tests, not a no-op (at minimum, import/compile check)make checkpasses on current branch
Formatting
- Platform-standard formatter is configured (
black,prettier,go fmt) - Default formatter config, only exception: four-space indents (except Go)
- All files pass
make fmt-check
Git Hygiene
- Pre-commit hook is installed (
make hooks) - No secrets in the repo (
.env, keys, credentials) - No mutable references in Dockerfiles or scripts (tags,
@latest) — all pinned by cryptographic hash with version/date comment - Using
yarn, notnpm(JS projects)
Directory Structure
- No unnecessary files in repo root
- Files organized into canonical subdirectories (
bin/,cmd/,docs/,internal/,static/, etc.) - Go migrations in
internal/db/migrations/and embedded in binary
HTTP Service Hardening (if targeting 1.0 and the repo is an HTTP/web service)
- Security headers set on all responses (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
- Request body size limits enforced on all endpoints
- Read/write/idle timeouts configured on the HTTP server (slowloris defense)
- Per-handler execution time limits in place
- Password-based auth endpoints are rate-limited
- CSRF tokens on all state-mutating HTML forms
- Passwords hashed with bcrypt, scrypt, or argon2
- Session cookies use HttpOnly, Secure, and SameSite attributes
- True client IP correctly detected behind reverse proxy (trusted proxy allowlist configured)
- CORS restricted to explicit origin allowlist for authenticated endpoints
- Error responses do not leak stack traces, SQL queries, or internal paths
Final
make checkpassesmake lintruns twice on an unchanged tree with the lint layerDONEboth times, neverCACHEDand never sub-secondscript/cibuildsucceeds (a baredocker build .ordocker build -f Dockerfile.lint .fails closed by design, on theCHECK_EPOCHguard)- Commit and merge fixes before starting your actual task