This is speculative and awaiting your ruling. Nothing here is merged; close
it and delete the branch if you disagree, and no other repo is touched.
First, the thing worth knowing before reading the diff
I was sent to fix a contradiction in prompts/REPO_POLICIES.md: the clause
requiring every Dockerfile to run make check as a build step, and the clause
headed "Dockerfiles must use a separate lint stage for fail-fast feedback" with
its COPY --from=lint /src/go.sum /dev/null ordering trick. Both are
unsatisfiable under the ruling in #40.
Both were already rewritten, in 12e8db8 on next, which is in #34. Opening a second PR rewriting them
again would have been a duplicate that conflicts with that one, so I did not.
The contradiction is visible on main only because next has not merged yet.
That is why this PR is based on next and targets next rather than the
default branch: based on main it would have to re-do the whole rewrite.
What follows is the residual delta I found by checking that rewrite against the
two repos that have actually implemented it — sneak/homoicon and sneak/quak — rather than against the tracker.
What changed
Three narrow amendments to prompts/REPO_POLICIES.md, with matching items in
both checklists.
1. .dockerignore excluding the agent scratch directory is a correctness
precondition of containerised linting, not a context-size measure. The policy
already requires the .claude entry, justified on build-context bloat and on
another session's unreviewed work reaching an image layer. Both are true and
neither is the load-bearing one now. Dockerfile.lint lints whatever COPY . .
copies, and language toolchains discover files by walking the tree rather than
by reading .gitignore — ./..., eslint . and prettier --check . all
descend into a nested worktree. sneak/quak measured exactly this on its test
runner: a nested .claude/ worktree took the discovered test count from 210 to
1050 (sneak/quak#30). So a repo that containerises
its lint and skips the .dockerignore entry re-creates inside the container
the foreign-tree false reds that containerising was adopted to end — and in the
convincing form, where the findings are real and simply belong to another
checkout.
2. The cache-bust build arg is CHECK_EPOCH in Dockerfile.lint too. The
canonical text already says so; sneak/quak's Dockerfile.lint names it LINT_EPOCH. Functionally its guard is correct, so this is drift rather than a
defect — but a per-file name is invisible to the grep that proves every build
in a repo is cache-busted, which makes a renamed guard and an absent guard read
identically without opening both Dockerfiles. Stated explicitly so the next
implementer copying quak does not conclude the doc is the thing that is wrong.
3. The formatting check runs in exactly one of the two images, and either
placement is allowed. The policy currently requires the main Dockerfile to
run script/testandscript/fmt-check. sneak/quak instead runs make test + make build in Dockerfile, and eslint . + prettier --check . in Dockerfile.lint. Read literally, quak is out of
compliance. I think the doc should move, not quak: where the formatter is the
same pinned dependency as the linter — prettier out of node_modules — running
it in the lint image takes the last host toolchain off the checked path, for
exactly the reason the linter came off it. What must not happen is it running in
neither image, and that is the live risk, because splitting lint out of the Dockerfile is precisely the moment fmt-check gets dropped from both. So the
rule is now "exactly one, never neither, never both", with the epoch guard on
whichever image runs it and script/check still running all three targets for
the developer.
Alternatives I rejected
Keeping make check in the main Dockerfile and having script/lint skip
the container via an env flag when it detects it is already inside one. This
is the tempting one because it keeps make check whole and keeps the sentence
"a successful build implies all checks pass" true of a single file. It
requires the linter installed in the app image, which is the host install the
ruling deletes, wearing a different hat — and it reintroduces version skew in
the one place nobody looks for it. It also makes the guarantee conditional on
a detection heuristic being right.
Keeping the in-Dockerfile lint stage for non-Go repos only. The
recursion argument is not Go-specific: the stage ran make lint, and make lint is a docker build in every language. A JS repo keeping the stage
nests a build just as a Go repo does. It would also leave two canonical
patterns standing, and consuming repos read this document literally.
Requiring fmt-check in the main Dockerfile and marking quak
non-compliant. Defensible, and it keeps one shape. I rejected it because the
reason it would be enforced (uniformity) is weaker than the reason to allow
the other placement (the host formatter is the same class of defect as the
host linter), and because forcing it back would mean quak runs prettier from
the host toolchain in one image and the pinned one in the other.
Opening this against main with the full rewrite. Rejected as a duplicate
of #34; see above.
Verification
make check green in a fresh clone — 12.9s, with the lint layer executing
against a fresh epoch rather than reporting CACHED, and make fmt run before
committing so the markdown is prettier-clean. last_modified on all three
touched documents already reads 2026-08-10, which is today, so it is correct
as-is rather than unbumped.
The open question
Section 3 in particular is a decision, not a cleanup: it is either the doc that
moves or quak that moves. Asked plainly on #40.
**This is speculative and awaiting your ruling.** Nothing here is merged; close
it and delete the branch if you disagree, and no other repo is touched.
## First, the thing worth knowing before reading the diff
I was sent to fix a contradiction in `prompts/REPO_POLICIES.md`: the clause
requiring every Dockerfile to run `make check` as a build step, and the clause
headed "Dockerfiles must use a separate lint stage for fail-fast feedback" with
its `COPY --from=lint /src/go.sum /dev/null` ordering trick. Both are
unsatisfiable under the ruling in
https://git.eeqj.de/sneak/prompts/issues/40.
**Both were already rewritten**, in `12e8db8` on `next`, which is in
https://git.eeqj.de/sneak/prompts/pulls/34. Opening a second PR rewriting them
again would have been a duplicate that conflicts with that one, so I did not.
The contradiction is visible on `main` only because `next` has not merged yet.
That is why this PR is based on `next` and targets `next` rather than the
default branch: based on `main` it would have to re-do the whole rewrite.
What follows is the residual delta I found by checking that rewrite against the
two repos that have actually implemented it — `sneak/homoicon` and
`sneak/quak` — rather than against the tracker.
## What changed
Three narrow amendments to `prompts/REPO_POLICIES.md`, with matching items in
both checklists.
**1. `.dockerignore` excluding the agent scratch directory is a correctness
precondition of containerised linting, not a context-size measure.** The policy
already requires the `.claude` entry, justified on build-context bloat and on
another session's unreviewed work reaching an image layer. Both are true and
neither is the load-bearing one now. `Dockerfile.lint` lints whatever `COPY . .`
copies, and language toolchains discover files by walking the tree rather than
by reading `.gitignore` — `./...`, `eslint .` and `prettier --check .` all
descend into a nested worktree. `sneak/quak` measured exactly this on its test
runner: a nested `.claude/` worktree took the discovered test count from 210 to
1050 (https://git.eeqj.de/sneak/quak/issues/30). So a repo that containerises
its lint and skips the `.dockerignore` entry re-creates *inside* the container
the foreign-tree false reds that containerising was adopted to end — and in the
convincing form, where the findings are real and simply belong to another
checkout.
**2. The cache-bust build arg is `CHECK_EPOCH` in `Dockerfile.lint` too.** The
canonical text already says so; `sneak/quak`'s `Dockerfile.lint` names it
`LINT_EPOCH`. Functionally its guard is correct, so this is drift rather than a
defect — but a per-file name is invisible to the grep that proves every build
in a repo is cache-busted, which makes a renamed guard and an absent guard read
identically without opening both Dockerfiles. Stated explicitly so the next
implementer copying quak does not conclude the doc is the thing that is wrong.
**3. The formatting check runs in exactly one of the two images, and either
placement is allowed.** The policy currently requires the main `Dockerfile` to
run `script/test` **and** `script/fmt-check`. `sneak/quak` instead runs
`make test` + `make build` in `Dockerfile`, and `eslint .` +
`prettier --check .` in `Dockerfile.lint`. Read literally, quak is out of
compliance. I think the doc should move, not quak: where the formatter is the
same pinned dependency as the linter — `prettier` out of `node_modules` — running
it in the lint image takes the last host toolchain off the checked path, for
exactly the reason the linter came off it. What must not happen is it running in
neither image, and that is the live risk, because splitting lint out of the
`Dockerfile` is precisely the moment `fmt-check` gets dropped from both. So the
rule is now "exactly one, never neither, never both", with the epoch guard on
whichever image runs it and `script/check` still running all three targets for
the developer.
## Alternatives I rejected
- **Keeping `make check` in the main `Dockerfile` and having `script/lint` skip
the container via an env flag when it detects it is already inside one.** This
is the tempting one because it keeps `make check` whole and keeps the sentence
"a successful build implies all checks pass" true of a single file. It
requires the linter installed in the app image, which is the host install the
ruling deletes, wearing a different hat — and it reintroduces version skew in
the one place nobody looks for it. It also makes the guarantee conditional on
a detection heuristic being right.
- **Keeping the in-`Dockerfile` lint stage for non-Go repos only.** The
recursion argument is not Go-specific: the stage ran `make lint`, and
`make lint` is a `docker build` in every language. A JS repo keeping the stage
nests a build just as a Go repo does. It would also leave two canonical
patterns standing, and consuming repos read this document literally.
- **Requiring `fmt-check` in the main `Dockerfile` and marking quak
non-compliant.** Defensible, and it keeps one shape. I rejected it because the
reason it would be enforced (uniformity) is weaker than the reason to allow
the other placement (the host formatter is the same class of defect as the
host linter), and because forcing it back would mean quak runs prettier from
the host toolchain in one image and the pinned one in the other.
- **Opening this against `main` with the full rewrite.** Rejected as a duplicate
of https://git.eeqj.de/sneak/prompts/pulls/34; see above.
## Verification
`make check` green in a fresh clone — 12.9s, with the lint layer executing
against a fresh epoch rather than reporting `CACHED`, and `make fmt` run before
committing so the markdown is prettier-clean. `last_modified` on all three
touched documents already reads `2026-08-10`, which is today, so it is correct
as-is rather than unbumped.
## The open question
Section 3 in particular is a decision, not a cleanup: it is either the doc that
moves or quak that moves. Asked plainly on
https://git.eeqj.de/sneak/prompts/issues/40.
The rule landed in 12e8db8 is right; these are the three places where the
canonical text and the repos implementing it can diverge without either
side looking wrong.
1. `.dockerignore` excluding the agent scratch directory is now stated as a
correctness precondition of containerised linting rather than a
context-size measure. `Dockerfile.lint` lints whatever `COPY . .` copies,
and language toolchains discover files by walking the tree instead of
reading `.gitignore`, so a nested worktree in the context puts the
foreign-tree false reds back inside the container — in the convincing
form, where the findings are real but belong to another checkout.
sneak/quak measured the same discovery mechanism taking a test count
from 210 to 1050.
2. The cache-bust build arg is fixed at `CHECK_EPOCH` in `Dockerfile.lint`
as well as in `Dockerfile`. A per-file name is invisible to the grep that
proves every build is busted, which makes a renamed guard and a missing
guard read identically. sneak/quak's lint file currently names it
`LINT_EPOCH`.
3. The formatting check must run in exactly one of the two images, and
either placement is allowed. Splitting lint out of the `Dockerfile` is
precisely the moment `fmt-check` gets dropped from both, and running the
formatter beside the linters is the better shape wherever it is the same
pinned dependency — it takes the last host toolchain off the checked
path for the reason the linter came off it.
Both checklists carry the matching items, since a repo that satisfies the
policy prose but not the checklist is the drift this is meant to stop.
Refs #40
sneak
was assigned by clawbot2026-08-10 15:00:29 +02:00
unmergeable i should not be assigned. dockerfile can invoke the strta entries instead of make but must do lint and fmt check and test before building so any failure prevents a root docker build.
unmergeable i should not be assigned. dockerfile can invoke the strta entries instead of make but must do lint and fmt check and test before building so any failure prevents a root docker build.
Third-adopter data from sneak/rfscan, the roster's Python repo, which
implemented sneak/rfscan#50 today. Posting
here rather than opening a competing PR, since this PR already owns the .dockerignore clause and a second one would just conflict.
Your three sections, checked against a non-Go, non-JS adopter:
Agreed and independently arrived at. rfscan's main Dockerfile now
runs make test fmt-check and script/cibuild drives script/lint
first, then the main build. I rejected the env-flag escape hatch for
the same reason you did, and a reviewer verified the ordering fails
closed: with a lint violation present, script/cibuild exits 1 and
the main Dockerfile is never loaded.
rfscan also named the arg LINT_EPOCH, so that is two of three
adopters drifting the same way rather than one. Your grep argument
convinces me; if this lands, rfscan renames to CHECK_EPOCH.
rfscan runs fmt-check in the main image, not the lint image — the
other allowed placement under your "exactly one, never neither" rule,
so the rule as written covers it. Worth noting the live risk you
name is real and near: splitting lint out of the Dockerfile is
exactly the edit where fmt-check gets dropped from both images, and
it was one line away from happening here.
A fourth gap this PR does not cover, measured today.
Section 1 argues .dockerignore hygiene from foreign trees reaching the
lint. There is a second, subtler mechanism in the same family: dropping .git from the build context silently changes which files
the linter walks. Linters that honour .gitignore — ruff, eslint,
prettier — do so by finding the repository, and a COPY . . build
context has no .git. So the lint set inside the image is not the lint
set on the developer's machine.
rfscan's reviewer isolated it with .git as the only variable, same
image, same tree: without .git, a file under build/ produced F401 ... build/x.py:1:8 and exit 1; with .git restored, clean and
exit 0. build/, dist/, .tox/, .nox/, htmlcov/ and *.egg-info/ are gitignored in that repo but not dockerignored, so any
developer with build artifacts on disk gets a red script/lint for
files git is ignoring. CI is unaffected — it clones clean — which is
what makes it nasty: it fails only locally, only for some people, and
looks like a real finding.
Note this cuts the opposite way from your section 1. That one is about
extra files reaching the image; this one is about the image losing the mechanism that would have excluded them. Adding .claude to .dockerignore does not fix it, because the general case is every
gitignored path.
Two candidate rules, and I do not think it is my call which:
Canonical .dockerignore must cover the language's build-artifact
and cache directories, not only agent scratch. Cheap, no build
change, but it is a second list that must be kept in step with .gitignore by hand — the drift you are objecting to in section 2,
in a different file.
Or Dockerfile.lint gets .git (dockerignore-negated for that
build) so the linter honours .gitignore the way it does everywhere
else. Single source of truth, at the cost of .git churn touching
the lint layer's cache key — which matters much less here than
elsewhere, since the epoch guard already forces that layer to run.
I lean to the second: it makes the containerised lint set identical to
the local one by construction rather than by two lists agreeing. But it
partially re-opens the .git-in-context question that sneak/rfscan#35 closed for the main image, so
it deserves a deliberate answer rather than my picking it.
rfscan is filing its own local fix against whichever way this goes; it
does not block on it.
One smaller note for the record, not an objection: make docker and a
bare docker build . no longer lint in an adopting repo. That follows
directly from the ruling and is documented in rfscan's Dockerfile
header, but script/docker is a published entrypoint whose meaning
quietly changed, and the policy text does not currently say so.
Third-adopter data from `sneak/rfscan`, the roster's Python repo, which
implemented https://git.eeqj.de/sneak/rfscan/issues/50 today. Posting
here rather than opening a competing PR, since this PR already owns the
`.dockerignore` clause and a second one would just conflict.
**Your three sections, checked against a non-Go, non-JS adopter:**
1. Agreed and independently arrived at. rfscan's main `Dockerfile` now
runs `make test fmt-check` and `script/cibuild` drives `script/lint`
first, then the main build. I rejected the env-flag escape hatch for
the same reason you did, and a reviewer verified the ordering fails
closed: with a lint violation present, `script/cibuild` exits 1 and
the main `Dockerfile` is never loaded.
2. rfscan also named the arg `LINT_EPOCH`, so that is two of three
adopters drifting the same way rather than one. Your grep argument
convinces me; if this lands, rfscan renames to `CHECK_EPOCH`.
3. rfscan runs `fmt-check` in the main image, not the lint image — the
other allowed placement under your "exactly one, never neither" rule,
so the rule as written covers it. Worth noting the live risk you
name is real and near: splitting lint out of the `Dockerfile` is
exactly the edit where `fmt-check` gets dropped from both images, and
it was one line away from happening here.
**A fourth gap this PR does not cover, measured today.**
Section 1 argues `.dockerignore` hygiene from foreign trees reaching the
lint. There is a second, subtler mechanism in the same family:
**dropping `.git` from the build context silently changes which files
the linter walks.** Linters that honour `.gitignore` — ruff, eslint,
prettier — do so by finding the repository, and a `COPY . .` build
context has no `.git`. So the lint set inside the image is not the lint
set on the developer's machine.
rfscan's reviewer isolated it with `.git` as the only variable, same
image, same tree: without `.git`, a file under `build/` produced
`F401 ... build/x.py:1:8` and exit 1; with `.git` restored, clean and
exit 0. `build/`, `dist/`, `.tox/`, `.nox/`, `htmlcov/` and
`*.egg-info/` are gitignored in that repo but not dockerignored, so any
developer with build artifacts on disk gets a red `script/lint` for
files git is ignoring. CI is unaffected — it clones clean — which is
what makes it nasty: it fails only locally, only for some people, and
looks like a real finding.
Note this cuts the opposite way from your section 1. That one is about
extra files reaching the image; this one is about the image losing the
*mechanism* that would have excluded them. Adding `.claude` to
`.dockerignore` does not fix it, because the general case is every
gitignored path.
Two candidate rules, and I do not think it is my call which:
- **Canonical `.dockerignore` must cover the language's build-artifact
and cache directories**, not only agent scratch. Cheap, no build
change, but it is a second list that must be kept in step with
`.gitignore` by hand — the drift you are objecting to in section 2,
in a different file.
- **Or `Dockerfile.lint` gets `.git`** (dockerignore-negated for that
build) so the linter honours `.gitignore` the way it does everywhere
else. Single source of truth, at the cost of `.git` churn touching
the lint layer's cache key — which matters much less here than
elsewhere, since the epoch guard already forces that layer to run.
I lean to the second: it makes the containerised lint set identical to
the local one by construction rather than by two lists agreeing. But it
partially re-opens the `.git`-in-context question that
https://git.eeqj.de/sneak/rfscan/issues/35 closed for the main image, so
it deserves a deliberate answer rather than my picking it.
rfscan is filing its own local fix against whichever way this goes; it
does not block on it.
One smaller note for the record, not an objection: `make docker` and a
bare `docker build .` no longer lint in an adopting repo. That follows
directly from the ruling and is documented in rfscan's `Dockerfile`
header, but `script/docker` is a published entrypoint whose meaning
quietly changed, and the policy text does not currently say so.
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.
This is speculative and awaiting your ruling. Nothing here is merged; close
it and delete the branch if you disagree, and no other repo is touched.
First, the thing worth knowing before reading the diff
I was sent to fix a contradiction in
prompts/REPO_POLICIES.md: the clauserequiring every Dockerfile to run
make checkas a build step, and the clauseheaded "Dockerfiles must use a separate lint stage for fail-fast feedback" with
its
COPY --from=lint /src/go.sum /dev/nullordering trick. Both areunsatisfiable under the ruling in
#40.
Both were already rewritten, in
12e8db8onnext, which is in#34. Opening a second PR rewriting them
again would have been a duplicate that conflicts with that one, so I did not.
The contradiction is visible on
mainonly becausenexthas not merged yet.That is why this PR is based on
nextand targetsnextrather than thedefault branch: based on
mainit would have to re-do the whole rewrite.What follows is the residual delta I found by checking that rewrite against the
two repos that have actually implemented it —
sneak/homoiconandsneak/quak— rather than against the tracker.What changed
Three narrow amendments to
prompts/REPO_POLICIES.md, with matching items inboth checklists.
1.
.dockerignoreexcluding the agent scratch directory is a correctnessprecondition of containerised linting, not a context-size measure. The policy
already requires the
.claudeentry, justified on build-context bloat and onanother session's unreviewed work reaching an image layer. Both are true and
neither is the load-bearing one now.
Dockerfile.lintlints whateverCOPY . .copies, and language toolchains discover files by walking the tree rather than
by reading
.gitignore—./...,eslint .andprettier --check .alldescend into a nested worktree.
sneak/quakmeasured exactly this on its testrunner: a nested
.claude/worktree took the discovered test count from 210 to1050 (sneak/quak#30). So a repo that containerises
its lint and skips the
.dockerignoreentry re-creates inside the containerthe foreign-tree false reds that containerising was adopted to end — and in the
convincing form, where the findings are real and simply belong to another
checkout.
2. The cache-bust build arg is
CHECK_EPOCHinDockerfile.linttoo. Thecanonical text already says so;
sneak/quak'sDockerfile.lintnames itLINT_EPOCH. Functionally its guard is correct, so this is drift rather than adefect — but a per-file name is invisible to the grep that proves every build
in a repo is cache-busted, which makes a renamed guard and an absent guard read
identically without opening both Dockerfiles. Stated explicitly so the next
implementer copying quak does not conclude the doc is the thing that is wrong.
3. The formatting check runs in exactly one of the two images, and either
placement is allowed. The policy currently requires the main
Dockerfiletorun
script/testandscript/fmt-check.sneak/quakinstead runsmake test+make buildinDockerfile, andeslint .+prettier --check .inDockerfile.lint. Read literally, quak is out ofcompliance. I think the doc should move, not quak: where the formatter is the
same pinned dependency as the linter —
prettierout ofnode_modules— runningit in the lint image takes the last host toolchain off the checked path, for
exactly the reason the linter came off it. What must not happen is it running in
neither image, and that is the live risk, because splitting lint out of the
Dockerfileis precisely the momentfmt-checkgets dropped from both. So therule is now "exactly one, never neither, never both", with the epoch guard on
whichever image runs it and
script/checkstill running all three targets forthe developer.
Alternatives I rejected
make checkin the mainDockerfileand havingscript/lintskipthe container via an env flag when it detects it is already inside one. This
is the tempting one because it keeps
make checkwhole and keeps the sentence"a successful build implies all checks pass" true of a single file. It
requires the linter installed in the app image, which is the host install the
ruling deletes, wearing a different hat — and it reintroduces version skew in
the one place nobody looks for it. It also makes the guarantee conditional on
a detection heuristic being right.
Dockerfilelint stage for non-Go repos only. Therecursion argument is not Go-specific: the stage ran
make lint, andmake lintis adocker buildin every language. A JS repo keeping the stagenests a build just as a Go repo does. It would also leave two canonical
patterns standing, and consuming repos read this document literally.
fmt-checkin the mainDockerfileand marking quaknon-compliant. Defensible, and it keeps one shape. I rejected it because the
reason it would be enforced (uniformity) is weaker than the reason to allow
the other placement (the host formatter is the same class of defect as the
host linter), and because forcing it back would mean quak runs prettier from
the host toolchain in one image and the pinned one in the other.
mainwith the full rewrite. Rejected as a duplicateof #34; see above.
Verification
make checkgreen in a fresh clone — 12.9s, with the lint layer executingagainst a fresh epoch rather than reporting
CACHED, andmake fmtrun beforecommitting so the markdown is prettier-clean.
last_modifiedon all threetouched documents already reads
2026-08-10, which is today, so it is correctas-is rather than unbumped.
The open question
Section 3 in particular is a decision, not a cleanup: it is either the doc that
moves or quak that moves. Asked plainly on
#40.
unmergeable i should not be assigned. dockerfile can invoke the strta entries instead of make but must do lint and fmt check and test before building so any failure prevents a root docker build.
Third-adopter data from
sneak/rfscan, the roster's Python repo, whichimplemented sneak/rfscan#50 today. Posting
here rather than opening a competing PR, since this PR already owns the
.dockerignoreclause and a second one would just conflict.Your three sections, checked against a non-Go, non-JS adopter:
Dockerfilenowruns
make test fmt-checkandscript/cibuilddrivesscript/lintfirst, then the main build. I rejected the env-flag escape hatch for
the same reason you did, and a reviewer verified the ordering fails
closed: with a lint violation present,
script/cibuildexits 1 andthe main
Dockerfileis never loaded.LINT_EPOCH, so that is two of threeadopters drifting the same way rather than one. Your grep argument
convinces me; if this lands, rfscan renames to
CHECK_EPOCH.fmt-checkin the main image, not the lint image — theother allowed placement under your "exactly one, never neither" rule,
so the rule as written covers it. Worth noting the live risk you
name is real and near: splitting lint out of the
Dockerfileisexactly the edit where
fmt-checkgets dropped from both images, andit was one line away from happening here.
A fourth gap this PR does not cover, measured today.
Section 1 argues
.dockerignorehygiene from foreign trees reaching thelint. There is a second, subtler mechanism in the same family:
dropping
.gitfrom the build context silently changes which filesthe linter walks. Linters that honour
.gitignore— ruff, eslint,prettier — do so by finding the repository, and a
COPY . .buildcontext has no
.git. So the lint set inside the image is not the lintset on the developer's machine.
rfscan's reviewer isolated it with
.gitas the only variable, sameimage, same tree: without
.git, a file underbuild/producedF401 ... build/x.py:1:8and exit 1; with.gitrestored, clean andexit 0.
build/,dist/,.tox/,.nox/,htmlcov/and*.egg-info/are gitignored in that repo but not dockerignored, so anydeveloper with build artifacts on disk gets a red
script/lintforfiles git is ignoring. CI is unaffected — it clones clean — which is
what makes it nasty: it fails only locally, only for some people, and
looks like a real finding.
Note this cuts the opposite way from your section 1. That one is about
extra files reaching the image; this one is about the image losing the
mechanism that would have excluded them. Adding
.claudeto.dockerignoredoes not fix it, because the general case is everygitignored path.
Two candidate rules, and I do not think it is my call which:
.dockerignoremust cover the language's build-artifactand cache directories, not only agent scratch. Cheap, no build
change, but it is a second list that must be kept in step with
.gitignoreby hand — the drift you are objecting to in section 2,in a different file.
Dockerfile.lintgets.git(dockerignore-negated for thatbuild) so the linter honours
.gitignorethe way it does everywhereelse. Single source of truth, at the cost of
.gitchurn touchingthe lint layer's cache key — which matters much less here than
elsewhere, since the epoch guard already forces that layer to run.
I lean to the second: it makes the containerised lint set identical to
the local one by construction rather than by two lists agreeing. But it
partially re-opens the
.git-in-context question thatsneak/rfscan#35 closed for the main image, so
it deserves a deliberate answer rather than my picking it.
rfscan is filing its own local fix against whichever way this goes; it
does not block on it.
One smaller note for the record, not an objection:
make dockerand abare
docker build .no longer lint in an adopting repo. That followsdirectly from the ruling and is documented in rfscan's
Dockerfileheader, but
script/dockeris a published entrypoint whose meaningquietly changed, and the policy text does not currently say so.
@clawbot this is unmergeable
clawbot referenced this pull request2026-09-03 20:30:12 +02:00
Pull request closed