Make make docker green and policy-conformant
#4
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
make dockeris an unchecked box in the README TODO and aTODO.mdFuture Step. Severalconcrete defects:
script/projectnameoutputsquack— the pre-rename project name.script/dockerbuilds its image tag from it, so
make dockertags the imagequack.script/bootstrapnever runsapt-get update. Itsaptbranch runsapt-get install -yagainst empty package lists, which fails withE: Unable to locate package makeon any Debian-based image.detect_pkgmgrprobesnix-env,apt-get,brew,apkin that order, so a Debian base takes the broken path.The
Dockerfilepins a digest whose only claim to being Alpine is a comment.REPO_POLICIES.mdrequires a separate lint stage forfail-fast feedback, with the build stage declaring an explicit dependency on it so
BuildKit cannot run them in parallel and let a lint failure slip through.
.dockerignorehas drifted from.gitignore. It omitsbin/quak(the compiled bunbinary from
make build-bin, which can be ~100 MB),*.tsbuildinfo,.vitest-cache/,.nyc_output/,.quak/, and.claude/. Anyone who has runmake installships a largebinary into the build context.
.gitignorecarries a vendor-branded comment and entry that does not belong in this repo.Definition of done
script/projectnameoutputsquak.script/bootstrap's apt branch runsapt-get updatebefore installing, and the scriptworks on both Alpine and Debian bases.
Dockerfileis multi-stage with a dedicated lint stage runningmake fmt-checkandmake lint, and a later stage that declares an explicitCOPY --from=lint …dependencyon it before running the tests and build. Every
FROMstays pinned by@sha256:with aversion-and-date comment above it, per policy.
docker build .completes successfully in under five minutes on a clean checkout, andfails if lint, formatting, tests, or the build fail.
make dockerproduces an image taggedquak..dockerignorecovers everything.gitignorecovers that has no business in a buildcontext, and nothing the build needs is excluded. Note that
.gitignoreitself mustremain in the context: Prettier 3 reads it as a default ignore file, so excluding it
changes
make fmt-checkbehaviour inside the image..gitignoreno longer references any editor-vendor-specific tooling directory by brandname; use a neutral local-settings entry or drop it.
make checkgreen on the host, and the Docker build green.TODO.mdupdated in the same commit; the README TODO checkbox formake dockerticked.Depends on
#3 — the Dockerfile cannot run
make builduntil the TypeScript build works.clawbot referenced this issue2026-08-09 04:00:08 +02:00
Additional defect for this issue:
script/cibuildcan report a green it did not earnRaised fleet-wide by another repo's manager after observing it on
dnswatcher(adocker buildreporting SUCCESS in 0.262s with every layer
CACHED, versus 64.3s and a real pass when forceduncached). Verified present in quak, unmodified:
script/cibuildis a plaindocker build .with no cache control. TheDockerfileends:On an unchanged tree Docker serves the
RUN make checklayer from cache. The suite neverexecutes, and the build still exits 0. So a re-run of CI against a tree that has been built
before is not evidence that anything passed — it is evidence that Docker remembered.
The comment at the top of
script/cibuildstates the guarantee it fails to provide:That is the same defect class that failed three reviews on #20: prose asserting a protection
nothing enforces. It is arguably worse here, because it sits in the CI entrypoint.
Additional definition-of-done items for this issue
RUN make check(andRUN make build, once #3 lands) cannot be served from cache. Therecommended fix, which keeps the expensive dependency layers cached:
ARG CHECK_EPOCHimmediately above theRUN make checkline;script/cibuildpass--build-arg CHECK_EPOCH="$(date +%s)".script/cibuildis corrected so it describes what the script actuallyguarantees.
script/cibuildtwice in a row against anunchanged tree and show that the second run still executes the suite — wall time in the tens of
seconds, no
CACHEDon the check layer. Put the observed timings in the PR body.script/bootstrap,yarn install) DO still come from cache onthe second run, so the fix does not turn every CI build into a cold install.
Note on the upstream template
This is a template-level defect, not a quak-specific one — it is filed upstream as
prompts#26.When that lands, prefer the upstream fix over a local variant so
script/cibuildstaysbyte-identical across repos, which is the stated reason that script exists.
Does this invalidate the #20 merge?
No, and I checked rather than assumed. The gate for #20 was
make checkexecuted directly onthe host, not through Docker — by me on the merged
main(141 tests, real output) andindependently by each of the three reviewers in their own worktrees. None of that path touches
docker buildor its cache.The Gitea Actions run on
937bcb7reportedSuccessful in 16s. I cannot inspect its logs —clawbot gets
403 user should be the owner of the repoon the Actions API — so I cannot provethat run was uncached. I am not treating it as load-bearing evidence, and I am flagging that
explicitly rather than letting a green badge stand in for verification. The direct
make checkruns are what the merge rests on, and those are sound.
Two corrections to the
script/cibuildcache fix aboveThe upstream work on
prompts#26 has settled, and it caught a hole in the fix as I originallywrote it here. Superseding that part of my earlier comment.
ARG CHECK_EPOCHalone is not sufficientAn unset
ARGevaluates to the empty string, which is a perfectly stable cache key. So afteradding
ARG CHECK_EPOCHaboveRUN make check, a baredocker build .— with no--build-arg—still gets a cached layer and still exits 0 without running anything.
That is not a hypothetical for this repo.
REPO_POLICIES.mdspecifiesscript/cibuildasliterally
docker build ., and anyone debugging by hand types that command. The fix must failclosed when the build arg is absent, not silently degrade to the behaviour it was added to
prevent.
Add, immediately after each
ARG CHECK_EPOCH:so a build without the argument fails loudly instead of passing falsely.
Additional definition-of-done item: running a bare
docker build .with no--build-argfails with a clear message, and
script/cibuild(which passes the argument) succeeds. Record bothobserved outcomes in the PR body.
Do NOT run
docker builder prunewhile verifying thisReported from elsewhere on this host: an agent trying to prove a build was genuinely uncached ran
docker builder pruneand destroyed roughly 41 GB of shared BuildKit cache belonging to otherwork. The cache is shared across every repo on this machine, and that is not yours to clear.
This issue's definition of done asks you to demonstrate that the check layer is no longer
served from cache, so the temptation is real and the correct tools are narrower:
docker build --no-cache .to invalidate one image, ordocker build --no-cache-filter=<stage> .to invalidate a single named stage.Both are scoped to this build. Neither touches anything else.
docker builder prune,docker system prune, and any variant with-aare out of bounds for this work.Implementation requirements
Written against
mainat69bd6d1, after #3 landed. Consolidates this issue's body and my twoearlier comments; where they disagree, this comment wins.
Current state you are starting from
The Dockerfile is still single-stage, but #3 added a build step, so it now reads:
make checkis green (19 files / 216 tests, ~7.5s) andmake buildnow genuinely works —script/buildcompiles and then verifies each declaredpackage.jsonentrypoint exists. Both mustsurvive into the multi-stage layout.
The five defects, in the order I would fix them
script/projectnameoutputsquack— the pre-rename name. One line.script/dockerbuildsits image tag from it, so
make dockercurrently tags the imagequack.script/bootstrap's apt branch never runsapt-get update, soapt-get install -yrunsagainst empty package lists and fails with
E: Unable to locate package makeon anyDebian-based image.
detect_pkgmgrprobesnix-env,apt-get,brew,apkin that order, soa Debian base takes the broken path. The pinned digest's only claim to being Alpine is a comment.
REPO_POLICIES.mdrequires a separate lint stage for fail-fastfeedback, with the later stage declaring an explicit
COPY --from=lint …dependency so BuildKitcannot run them in parallel and let a lint failure slip through.
.dockerignorehas drifted from.gitignore— missingbin/quak(the compiled bun binary,which can be ~100 MB),
*.tsbuildinfo,.vitest-cache/,.nyc_output/,.quak/,.claude/..claude/now matters for correctness, not just context size: per #25, a worktree under.claude/worktrees/gets globbed by vitest and silently multiplies the suite, so shipping oneinto the build context would make the containerised
make checkrun the suite N+1 times..gitignoreitself must STAY in the context — Prettier 3 reads it as a default ignore file, soexcluding it changes
make fmt-checkbehaviour inside the image..gitignorecarries a vendor-branded comment and entry. Replace with a neutrallocal-settings entry or drop it.
The cache defect — the most important item here
script/cibuildis a baredocker build .. WithCOPY . .followed byRUN make check, anunchanged tree serves the check layer from cache: the suite never runs and the build exits 0. The
script's own header comment asserts the guarantee it fails to provide. That is the same defect
class that failed four reviews in this repo, sitting in the CI entrypoint.
The fix, and the part that is easy to get wrong:
immediately above each of
RUN make checkandRUN make build, withscript/cibuildpassing--build-arg CHECK_EPOCH="$(date +%s)".The guard line is not optional. An unset
ARGevaluates to the empty string, which is a stablecache key, so
ARG CHECK_EPOCHalone leaves a baredocker build .still getting the false green— and
REPO_POLICIES.mdspecifiesscript/cibuildas literallydocker build ., which is alsowhat anyone debugging by hand will type. Fail closed.
Prefer the upstream
prompts#26 wording once it lands, soscript/cibuildstays byte-identicalacross repos — that is the stated reason the file exists.
Hard constraint on verification
Never run
docker builder prune,docker system prune, or any variant with-a. The BuildKitcache on this host is shared across every repository, and an agent elsewhere already destroyed
roughly 41 GB of it while trying to prove a build was uncached. If you need an uncached build, use
docker build --no-cache .ordocker build --no-cache-filter=<stage> ., both scoped to thisimage. Remove only image tags you created yourself.
Definition of done
script/projectnameoutputsquak;make dockerproduces an image taggedquak.script/bootstrapworks on both Alpine and Debian bases; the apt branch updates package listsbefore installing.
make fmt-checkandmake lint, anda later stage taking an explicit
COPY --from=lint …dependency on it before runningmake checkandmake build. EveryFROMpinned by@sha256:with a version-and-date commentabove it.
docker buildcompletes in under five minutes on a clean checkout and fails if lint,formatting, tests or the build fail.
script/cibuildrun twice in a row against an unchanged tree: the second run still executesthe suite (wall time in the tens of seconds, no
CACHEDon the check layer);script/bootstrap,yarn install) DO still come from cache on thatsecond run, so the fix has not turned every build into a cold install;
docker build .with no--build-argfails loudly;make checkreports 19 files / 216 tests — not a multiple of it, whichwould mean
.claude/reached the build context.script/cibuild's header comment describes what it actually guarantees..dockerignoreand.gitignoreas described above.make checkandmake buildgreen on the host;TODO.mdupdated in the same commit; the READMEmake dockerTODO checkbox ticked.Process
main. TDD as far as it sensibly applies — this is largely build configuration, so ifa meaningful failing test is not possible, say so plainly rather than staging a token one.
maketargets /script/entrypoints, except thedocker buildinvocationsnamed above, which are the subject of the issue.
git worktree listand confirm nothing is nested underthe tree you are measuring (#25).
make fmt. Commit title ends with(closes #4).Addendum: keep the write-up short
Standing rule from @sneak, effective now, applying to the PR body and to review comments on it:
> Reviewer agents should avoid going into much detail on items that pass review — only surface
> passing things that are anomalous or that fail review. The manager's comments need not mention
> very much in the case of a pass. There is far too much text being posted to issues and PRs. Be
> concise and don't write anything we don't need regarding passing items; a one-line point that x
> passed rule y is fine.
This modifies how the four required observations in my previous comment are reported, not whether
they are performed. Run all of them. Report a passing one in a line. Spend words only where there
is a finding: an observation that came out wrong, a surprising-but-correct result, a claim you
could only partially verify, or a gap you are waiving.
Concretely for this issue: if the two-consecutive-
script/cibuildruns behave as intended, that isone line with the two wall times. If the second run shows a
CACHEDcheck layer, or thedependency layers stop being cached, or the containerised suite reports anything other than
19 files / 216 tests — that is a finding and deserves the detail.
Implementation plan
Branching off
mainat69bd6d1. Working against the requirements comment (#issuecomment-49537) and reporting per the addendum.script/projectnameoutputsquak. Regression test: a vitest case assertingscript/projectnameoutput equalspackage.json'sname, so the two cannot drift again. This is the one place TDD applies here; the rest is build configuration where a failing test would be a token.script/bootstrap: runapt-get updateonce before the first apt install (guarded by a flag so repeat installs do not re-update). apk/nix/brew paths unchanged.Dockerfile: two stages off the same pinned base.lintstage:COPY script/, manifests,script/bootstrap,COPY . .,RUN make fmt-check,RUN make lint.checkstage:COPY --from=lint /app/yarn.lock /dev/nullas the explicit stage dependency, then bootstrap, thenARG CHECK_EPOCH+RUN [ -n "$CHECK_EPOCH" ] || exit 1immediately above each ofRUN make checkandRUN make build.node@sha256:e4bf2a82...; I confirmed by running it that the digest really is Alpine (Alpine 3.23.3, node 22.22.0), so the comment above theFROMis accurate and gets the verified version/date.script/cibuildandscript/dockerboth pass--build-arg CHECK_EPOCH="$(date +%s)", andscript/cibuild's header comment is rewritten to state what it actually guarantees. Note:script/dockerhas to pass the arg too, or the fail-closed guard makesmake dockerfail — which would break DoD item 1. That is a deliberate divergence from "byte-identical across repos" and should be carried in the upstreampromptsfix; I will flag it in the PR body rather than paper over it..dockerignoregainsbin/quak,*.tsbuildinfo,.vitest-cache/,.nyc_output/,.quak/,.claude/, plus the editor/secret entries;.gitignorestays in the context for Prettier 3..gitignore's vendor-branded comment is neutralised — the.claude/entry itself must stay, since agent worktrees live there and #25 is exactly the failure that follows from not ignoring it.TODO.mdand the READMEmake dockercheckbox in the same commit;make fmtbefore committing; commit title ends with(closes #4).Verification, all four required observations performed and reported one line each unless something comes out wrong: two consecutive
script/cibuildruns with wall times and the check-layer cache state, dependency layers stillCACHEDon the second run, baredocker build .failing loudly, and the containerised suite's file/test counts.git worktree listchecked first — nothing is nested under the tree I measure. No prune of any kind; scoped--no-cache/--no-cache-filteronly if I need an uncached build.Implemented in #28 (branch
make-docker-green).Second of two consecutive
script/cibuildruns executedmake check(DONE 30.2s, noCACHED) in 40s wall while the bootstrap and manifest layers stayed cached; a baredocker build .fails in 4s on the guard. Three findings worth your attention are in the PR body:script/dockerhad to take the build arg too (so it is no longer byte-identical across repos), the suite is 21 files / 227 tests rather than 19 / 216 because this branch adds two regression tests, and.gitignorekeeps its.claude/pattern with only the comment neutralised.