next: accumulated work for the current cycle #39
Reference in New Issue
Block a user
Delete Branch "next"
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?
Long-lived
nextbranch for this release cycle. One issue-closing commit per work unit accumulates here; this PR is the single integration point back tomain.Carried so far
407b0a0— Add the MITLICENSEand state it in the README (closes #10)25b6c0a— Run the lint inside Docker viaDockerfile.lint(closes #38). Amends and replaces the earlierf5761b6, per the review on this PR and the owner ruling on the issue.407b0a0— MITLICENSEThe repo had no
LICENSE, whichREPO_POLICIES.mdlists as a mandatory minimum file, and the README's License section said "Content is provided as-is for community use." — a sentence that granted nothing explicitly and matched no committed file.The repo is public (
private: falseon the Gitea API, checked rather than assumed), so the owner's standing policy applies: MIT on any public repo lacking a licence.LICENSEat the repo root, extensionless. Byte-identical to the canonicalsneak/homoiconcopy — same git blob,3274443— and its body is word-for-word the SPDX MIT text with only the line wrapping differing.## Licensenow readsMIT. See [LICENSE](LICENSE)., and says explicitly that the licence coverscontent/as well as the code. This repo carries both a Hugo site and its community content, while MIT's own text speaks only of "the Software".REPO_POLICIES.mdREADME requirements call for and which was the one field it was missing.TODO.mdupdated in the same commit per its Workflow section.Per-file licence headers and SPDX identifiers are deliberately omitted — no org standard mandates them, and the issue puts them explicitly out of scope.
Verification
script/cibuildgreen in-container on the pinned standardhugo v0.164.0, with the check layer observed executing rather than served from cache (CHECK_EPOCHguard satisfied, real build output); thescript/bootstraplayer stayedCACHEDas expected.public/tree —index.html,css/style.css,index.xml,sitemap.xml— contains no copyright, all-rights-reserved or terms-of-use string at all. The rendered footer names@sneakand links the repo but reserves no rights; the RSS carries nocopyrightelement;content/_index.mdis factual mesh channel data asserting no licence of its own..prettierignorescope question, likewise as asked, measured rather than assumed:LICENSEneeds no ignore entry.script/fmtpasses prettier the explicit globs'**/*.md'and'**/*.css', and an extensionless root file matches neither. Ascript/fmtrun leavesLICENSE's hash unchanged at3274443, while a counterfactualLICENSE.mdcopy placed alongside it was reflowed by that same run — direct evidence that the extension is what excludes it, not an ignore rule. The counterfactual file was removed and is not in the commit.25b6c0a— the lint runs in DockerNew root
Dockerfile.lintrunshugo --minify --printPathWarningsas a build step, so a successful build is a clean lint.script/lintis nothing but a build of that file — no host path, and deliberately no "am I already inside a container?" branch, which would be a host lint path in disguise.The containerisation boundary is lint only, per the owner ruling on the issue: "fmt and fmt check arent docker, just linting."
script/fmtandscript/fmt-checkrun on the host, with the version, scope and flags they had onmain. That also deletes the forced duplication of prettier's settings between a script and a Dockerfile, and with it the keep-in-sync notes that duplication required. It lands the repo on the same shape as the canonicalsneak/promptstext, which independently kept the format check on the host.One stage, on purpose
A whole-file
docker build -f Dockerfile.lint .builds only the file's last stage, and sibling stages off a shared base carry no ordering edge between them. A second stage beside the lint would therefore be silently skipped by exactly the invocation the canonical org-widescript/lintuses, and the per-stageCHECK_EPOCHguard cannot catch that — the stage that did run satisfies it. With a single stage there is nothing to skip andscript/lintneeds no--target. The file carries a comment saying that any second check added here must be chained (FROM lint AS ...) or carry an explicit ordering edge, never left as a sibling.Its first four instructions are byte-identical to the main
Dockerfile's and in the same order, so the expensiveRUN script/bootstraplayer — which compiles the pinned Hugo from source — is shared between the two images rather than paid twice. Observed, not assumed: in onescript/cibuildinvocation,RUN script/bootstrapcame backCACHEDin both builds.The recursion, resolved by direction rather than detection
make checkcallsscript/lint, andscript/lintis adocker build, soRUN make checkin an image would attempt a docker build inside a build step where there is no daemon. So:hugo --minify --printPathWarnings)Dockerfile.lintscript/linthugo --minify)Dockerfile,RUN script/testscript/test,script/docker,script/cibuildDockerfile,RUN script/fmt-check; and on the hostscript/fmt-check--write)script/fmtThe main
Dockerfileruns the individual non-lint checks as separateRUNlines under theCHECK_EPOCHguard, matching the canonical shape, and only the lint is absent from it.script/cibuildrunsscript/lintfirst, for fail-fast feedback: on a runner with no cached bootstrap layer a lint failure should not wait behind a Hugo build from source.script/checkis unchanged in behaviour (test,lint,fmt-check); one of its three steps is a container build, so it needs a working Docker daemon and has no fallback.Cache busting
ARG CHECK_EPOCHwith no default, guarded by[ -n "$CHECK_EPOCH" ] || exit 1, and the value expanded into the linted command as well as the guard, so invalidation never rests on BuildKit's treatment of an unreferencedARG. Every image-building entrypoint generates and passes it —script/cibuild,script/docker,script/lint— each as a whole assignment rather than inline, for theset -ereasonscript/cibuilddocuments.script/lintbuilds with--output type=cacheonly. The build is run for its exit status, not for an image; because the lint layer is cache-busted every invocation, an exporting build leaves one dangling image per lint run, which accumulates on a shared host. The build cache is unaffected (script/bootstrapstill hits) and failures still propagate.Divergences from
REPO_POLICIES.md, both statedREPO_POLICIES.md:92, "all Dockerfiles must runmake check". That rule and "every lint run happens in Docker" cannot both hold oncemake checkcontains the lint.REPO_POLICIES.md:102-168, which requires a separate lint stage whose result the build stage depends on viaCOPY --from=lint /src/go.sum /dev/null, on the stated grounds that without the edge "the build stage would not wait for lint to finish and a lint failure might not fail the overall build". No such edge exists here: the lint is its own file and its own build, sequenced byscript/cibuildrather than by BuildKit.Both sections are superseded upstream by
12e8db8insneak/prompts(carried by sneak/prompts#34), which deletes the Go multistage lint stage and its ordering trick for the same reason — that stage ranmake lint, which is now a docker build.Verification
Re-established from scratch in a fresh clone against this commit's tree; none of it is inherited from the superseded
f5761b6.script/lintruns, unchanged tree, both executed the lint. Run 1 68.6s (coldscript/bootstrap), run 2 0.849s withRUN script/bootstrapCACHEDand the lint layerDONE, printing its own distinct epoch and hugo's real output (Start building sites …,hugo v0.164.0 linux/amd64, the page table,Total in 23 ms). No lint layer was everCACHED.docker build -f Dockerfile.lint --build-arg CHECK_EPOCH=... ., no--target, genuinely ran the lint — epoch echoed, real hugo output,[7/7]DONE. That same run'snaming to moby-dangling@sha256:...line is the direct evidence for the dangling-image leak that--output type=cacheonlyremoves fromscript/lint.docker build -f Dockerfile.lint .with no argument dies onRUN [ -n "$CHECK_EPOCH" ] || exit 1, exit 1, before the lint.themes/loravega/layouts/index.htmlfailed the build with hugo's own finding —executing "index.html" at <.ThisFieldDoesNotExistRw38>: can't evaluate field ThisFieldDoesNotExistRw38 in type *hugolib.pageState. Reverted, re-run clean.script/fmt-check: an over-long unwrapped line appended toREADME.mdgave exit 1 with[warn] README.md/Code style issues found in the above file. Reverted,All matched files use Prettier code style!.script/cibuildfails fast: with that same template error planted, it exited 1 in 0.630s, and the main image build never started — zerocheck epochlines in the whole log.script/cibuildgreen: 11.5s, exit 0. Lint build first with its own epoch and real hugo output; then the main image with a distinctcheck epoch,script/testoutput, andRUN script/fmt-checkprintingAll matched files use Prettier code style!.RUN script/bootstrapCACHEDin both.make checkgreen: 3.9s, exit 0, with the host production build, the container lint (distinct epoch, real hugo output) and the host format check each observed executing.script/dockergreen: 10.3s, exit 0, bothRUNchecks observed executing,naming to docker.io/library/lora.vegas:latest.script/lintrun was bracketed by a set-diff ofdocker images -f dangling=true -q. The one new ID that appeared was attributed bydocker inspectto another concurrent session (node base,WorkingDir=/app), not to this build;script/lintexported no image..gitea/,script/bootstrap,script/testand.dockerignoreare byte-identical tomainat7d7bec5.deploy.ymlreferences no Dockerfile, nomake checkand no docker build.Known limitation, unchanged by this commit
The lint fails on hugo build errors but not on render-target collisions:
--printPathWarningsprints them and exits 0. Pre-existing, tracked at #25, and containerising the run neither fixes nor worsens it. Called out because it bounds what the lint negative control could demonstrate.Out of scope, untouched
.gitea/workflows/deploy.ymland the deploy path.check.ymlis unchanged — it already runsscript/cibuild.Manager note.
407b0a0is docs-only —LICENSE,README.md,TODO.md, verified against the diff, with zero changes to code, config, scripts, CI or build files — so the adversarial review is waived per policy.LICENSE's blob hash matches the canonicalsneak/homoiconcopy exactly, so the licence text needs no independent reading.Not merging on that exception, deliberately. A merge to
mainfiresdeploy.ymland publishes a real Cloudflare Pages deployment, and this commit changes nothing Hugo renders — the built output is byte-identical. Spending a production deploy on a README edit is the wrong trade on a repo where a merge has broken the deploy before. It accumulates here until the cycle lands.One defect carried in and to be fixed by the next commit rather than reworked now:
TODO.md's Next Step was advanced to #20 , which is parked with the owner and gated on this Gitea instance serving the v4 artifact protocol. The implementer followed the documented Workflow correctly — the fault is that Future Steps is ordered with owner-blocked items at the top, so the mechanical "promote the top item" step surfaces unstartable work.Independent review —
f5761b6only (407b0a0out of scope)Verdict: FAIL —
needs-rework.Reviewed in an own fresh clone at
next=f5761b6. CI status on the head commit issuccess;nextis a fast-forward ofmain(mergeable, no conflicts).Blocking
1.
script/fmt-checkmust not be a container build. Owner ruling, sneak 2026-08-10: "fmt and fmt check arent docker, just linting." Only linting is containerised. This is settled by decision, so the (well-argued) rationale in the PR body does not survive it.Acceptable shape: restore
script/fmt-checkto its7d7bec5host form —PRETTIER_VERSION="3.4.2"andnpx --yes "prettier@${PRETTIER_VERSION}" --check '**/*.md' '**/*.css' --tab-width 4 --prose-wrap always— drop itsCHECK_EPOCHgeneration, and delete thefmt-checkstage fromDockerfile.lintentirely. That also deletes the forced duplication of prettier's version/scope/flags betweenscript/fmtandDockerfile.lint, so the keep-in-sync notes on both sides go with it, as do the fmt-check sentences inscript/fmt,script/check,README.mdandTODO.md.2.
Dockerfilenow gates on strictly less than it did, and less than canonical. It ranmake check(test + lint + fmt-check);Dockerfile:56now runsmake testalone. Dropping lint is forced by the recursion. Droppingfmt-checkis not — once finding 1 lands there is no recursion in running it inside the image, and the canonical resolution upstream (12e8db8onnextinsneak/prompts, carried by sneak/prompts#34) runs bothscript/testandscript/fmt-checkin the main image.Acceptable: main
Dockerfilerunsscript/testandscript/fmt-checkas separateRUNlines under theCHECK_EPOCHguard, with the existing header comment narrowed to say that only lint is deliberately absent and why. Update the README Entrypoints paragraph to match.3.
Dockerfile.lint's default stage isfmt-check, so the conventional whole-file build runs no lint at all and still exits 0.fmt-checkis the lastFROMin the file, and the two stages are independent siblings offbasewith no ordering edge, so a build without--targetnever instantiates thelintstage. Proven, not inferred:The
CHECK_EPOCHguard does not protect against this — it is per-stage, and the stage that did build satisfied it.Dockerfile.lint:2-3("the checks are build steps, so a successful build IS a clean lint") is therefore false for exactly the invocation the org-wide canonicalscript/lintuses (docker build -f Dockerfile.lint ., no--target). A future sync toward the canonical script form would silently drop the hugo lint with no failure and no output.Acceptable: after finding 1 the file contains only the lint, so
script/lintbecomes a plaindocker build -f Dockerfile.lint --build-arg CHECK_EPOCH="$epoch" .with no--target, and the last-stage hazard disappears. If any second stage is ever reintroduced, it must be chained (FROM lint AS ...) or carry an explicit ordering edge, never left as a sibling.4.
script/cibuildordering diverges from canonical, undisclosed.script/cibuild:41-45builds the main image first, thenscript/lint. Canonical runsscript/lintfirst for fail-fast. On a runner with no cachedRUN script/bootstraplayer, a lint-only failure is reported only after the full Hugo-from-source main build completes. Acceptable:script/lintfirst, then the main image build, then the (host)script/fmt-check; each build keeps its own epoch.5. Undisclosed
REPO_POLICIES.mddivergence. The PR disclosesREPO_POLICIES.md:92("all Dockerfiles must runmake check"). It does not discloseREPO_POLICIES.md:102-168, which requires the Dockerfile to carry a separate lint stage with an explicit ordering edge (COPY --from=lint /src/go.sum /dev/null) precisely because "without this line, the build stage would not wait for lint to finish and a lint failure might not fail the overall build". After this commit no ordering edge exists anywhere — which is what finding 3 is. Upstream has already superseded that section (12e8db8insneak/prompts), so the right resolution is to state the divergence and link the superseding change, not to re-add the Go lint stage. But it has to be stated.Non-blocking, worth fixing while in there
npx --yes prettier@3.4.2re-fetches from the npm registry every single run — there is no surviving npx cache in that layer. Proven against the builtbaseimage:docker run --rm --network none ... npx --yes prettier@3.4.2givesnpm error request to https://registry.npmjs.org/prettier failed, reason: getaddrinfo EAI_AGAIN. The host form reuses~/.npm/_npxand works offline after first use, so as committed every commit and every CI run requires npm-registry reachability.script/lint.make checknow leaves 2 per run and CI 3 per push, against 1 before. Consider-t "$("$SCRIPT_DIR"/projectname)-lint".make check7.9s, against 3.6s for the equivalent all-host gate at7d7bec5. That is fine. What is not stated anywhere is the cold case: with no cachedscript/bootstraplayer the first commit on a machine compiles the pinned Hugo from source before the hook returns.script/checkand the README should say so.Verified and passing (one line each)
script/lintruns on an unchanged tree, 7.334s then 1.913s, distinct epochs echoed, real hugo output both times,[base 4/5] RUN script/bootstrapCACHEDand no lint layer everCACHED; baredocker build .and baredocker build -f Dockerfile.lint .both die onRUN [ -n "$CHECK_EPOCH" ] || exit 1.script/cibuild,script/docker,script/lint,script/fmt-check— and no other entrypoint builds an image (checked all ofscript/, theMakefile, and both workflows, not just the four the PR body names).baseimage contains exactly the same six md/css files as the host worktree (README.md,REPO_POLICIES.md,TODO.md,archetypes/default.md,content/_index.md,themes/loravega/static/css/style.css), both regimes read the same.gitignore+.prettierignore, and no md/css path in this repo falls under any.dockerignore-only exclusion. The container set is not smaller.f5761b6touches neitherscript/bootstrap,script/test,.dockerignorenor.gitea/workflows/*;deploy.yml's build job runs onlyscript/bootstrapthenscript/teston a runner container and never references a Dockerfile,make check, or a docker build. Checked against the commit's file list, not inferred from the PR body.script/cibuildstill reaches the production build, the lint and the format check (the loss in finding 2 is to the main image, not to CI).make fmtclean; tree clean;make checkgreen; commit message ends(closes #38); no attribution trailers anywhere in the commit; naming and idiom consistent with the surrounding scripts; no scope creep; theTODO.mdFuture Steps reordering correctly fixes the "owner-blocked items at the top" defect carried in from407b0a0.--printPathWarningslimitation (collisions print but exit 0) is correctly disclosed as pre-existing and tracked at #25 .Disclosures
successforf5761b6, but this account cannot read the Actions job log (403: user should be the owner of the repo), so I could not independently confirm from CI output that the three check layers executed there rather than being served from cache. I verified the mechanism locally instead — each entrypoint mints a fresh epoch per invocation, which structurally prevents it — but the CI-side execution itself is asserted by the author and unverified by me.docker run --network noneagainst the builtbaseimage rather thandocker build --network none, because changing the build network mode invalidates thescript/bootstrapcache key and the build failed inscript/bootstrapbefore reaching npx.Manager note. Review FAILED;
needs-rework. Rework dispatched to a fresh implementer, not the author.I independently confirmed finding 3 against the committed
Dockerfile.lintbefore acting on it, because it is the one finding that is a live false green rather than a policy divergence:fmt-checkis the final stage andlintis its sibling offbase, so a whole-file build instantiates onlyfmt-checkand exits 0 having run no lint at all. The per-stageCHECK_EPOCHguard cannot catch this — it is satisfied by the stage that did run. The file's own header claims "a successful build IS a clean lint", which is false for exactly the invocation the canonical org-widescript/lintuses (docker build -f Dockerfile.lint ., no--target). This repo'sscript/lintpasses--target lintand so was green for the right reason; the trap was one entrypoint away, not active.Finding 1 resolves finding 3 structurally rather than by patching it: with
script/fmt-checkback on the host per the owner ruling,Dockerfile.linthas one stage,--targetbecomes unnecessary, and there is no non-final stage left to skip. The reworker is instructed to take that route rather than adding a--targetand calling it fixed.Rework lands as an amend of
f5761b6plus a force-push ofnext, keeping one commit per closed issue. That is safe only while nothing is based on the branch, so the reworker verifies immediately before pushing thatnextis still atf5761b6and this PR is still open, and aborts to me if either has moved.f5761b6227to25b6c0a9deRework of
f5761b6, point by pointAmended to
25b6c0a, force-pushed overnext. Preconditions were checked immediately before the push and held:origin/nextwas still exactlyf5761b6227308afd11c51367b1739a62122768ffand this PR was still open and unmerged; the push used--force-with-leasepinned to that SHA.407b0a0survives untouched as the parent. Reworked by a different implementer than the author; every finding was re-verified from scratch in a fresh clone, nothing was inherited from the old evidence.1.
script/fmt-checkmust not be a container build — fixedRestored to its
7d7bec5host form:PRETTIER_VERSION="3.4.2"andnpx --yes "prettier@${PRETTIER_VERSION}" --check '**/*.md' '**/*.css' --tab-width 4 --prose-wrap always, with itsCHECK_EPOCHgeneration gone. Thefmt-checkstage is deleted fromDockerfile.lint, and with it the duplicated prettier version/scope/flags and both keep-in-sync notes. The fmt-check-in-Docker sentences are gone fromscript/fmt,script/check,README.mdandTODO.md. Each of the four now states the boundary the ruling actually draws — only linting is containerised — rather than the old split.2.
Dockerfilegates on less than canonical — fixedIt now runs
script/testandscript/fmt-checkas separateRUNlines under theCHECK_EPOCHguard, matching12e8db8insneak/prompts. The header comment is narrowed accordingly: it says that only the lint is deliberately absent, why (script/lintis adocker build, somake checkhere would nest a build inside a build step), and that the lint is not skipped becausescript/cibuildruns it first in its own container. The README Entrypoints paragraph matches.Observed executing in the main image on a green
script/cibuild:3. Whole-file build ran no lint — fixed structurally, not patched
Taken the route the manager note directs: with finding 1 landed,
Dockerfile.linthas exactly one stage,script/lintpasses no--target, and there is no sibling left to skip. No--targetwas added anywhere. The file now carries a comment stating that a whole-file build instantiates only the last stage, that siblings off a shared base have no ordering edge, and that any second check added here must be chained or carry an explicit edge.Regression test — the exact invocation the canonical
script/lintuses, no--target, now genuinely lints:The header's "a successful build IS a clean lint" claim is now true for every invocation of the file, not just one caller's.
4.
script/cibuildordering — fixedscript/lintfirst, then the main image build. Each keeps its own epoch. Proven fail-fast rather than asserted: with a template error planted,script/cibuildexited 1 in 0.630s and the main image build never started — zerocheck epochlines in the entire log, so nothing waited behind a Hugo-from-source build.5. Undisclosed
REPO_POLICIES.md:102-168divergence — addedThe commit body now states both divergences. The second reads:
REPO_POLICIES.md:102-168requires a separate lint stage whose result the build stage depends on throughCOPY --from=lint /src/go.sum /dev/null, on the stated grounds that without the edge "the build stage would not wait for lint to finish and a lint failure might not fail the overall build"; no such edge exists here, because the lint is its own file and its own build, sequenced byscript/cibuildrather than by BuildKit. Both sections are noted as superseded upstream by12e8db8insneak/prompts(sneak/prompts#34), which deletes the Go multistage lint stage and its ordering trick for the same reason. The Go lint stage was not re-added.Non-blocking items
script/fmt-checkis back on the host and reuses~/.npm/_npx. One residual, stated plainly rather than left implicit: the mainDockerfile'sRUN script/fmt-checksits below theCHECK_EPOCHguard, so it re-fetches prettier from the registry on every image build. That is not new — the pre-f5761b6RUN make checkdid exactly the same — and it applies only to the image build, not tomake checkor the pre-commit hook.script/lintnow builds with--output type=cacheonly: the build is run for its exit status, not for an image, so nothing is exported at all. Tagging would only have converted the leak from one dangling image per run into one dangling image per run plus a tag, since the cache-busted lint layer produces a new image ID every time. Measured: ascript/lintrun bracketed by a set-diff ofdocker images -f dangling=true -qproduced one new ID, whichdocker inspectattributed to another concurrent session (node base,WorkingDir=/app,NODE_VERSION=22.22.0) and not to this build. The build cache is untouched —RUN script/bootstrapstill comes backCACHED— and failures still propagate (empty epoch and a failing lint both exit 1 undercacheonly). This is a deliberate deviation from the canonicalscript/lint's baredocker build; on this host there were already 741 dangling images when the rework started, so it earns its keep. The main image build inscript/cibuildandscript/dockerstill exports, correctly —script/dockerproduces the tagged artifact and that is its purpose.script/check's header now says that the first lint on a machine with no cachedscript/bootstraplayer compiles the pinned Hugo from source, that this cost falls on the pre-commit hook too, and that later runs reuse the layer. The README says the same in the usage section. Measured here: coldscript/lint68.6s on a host with a warm Go module cache, warmscript/lint0.849s,make check3.9s end to end.Nothing I disagree with
All five blocking findings were accepted as written. One point of interpretation worth naming: finding 2 as phrased could be read as putting the format check only in the image, but
script/checkstill runs it on the host too, so the pre-commit gate does not require a Docker daemon for formatting — only for the lint. That matches canonical and I read it as the intent.Full re-verification
Complete evidence is in the PR body's
25b6c0asection. Summary: two consecutivescript/lintruns on an unchanged tree both executed hugo for real with distinct epochs andscript/bootstrapCACHED(second run 0.849s, no lint layer everCACHED); the no---targetwhole-file build lints; a bare argument-less build fails closed on the guard; negative controls for the container lint and the host format check both failed with their specific finding and both went clean on revert;make check(3.9s),script/docker(10.3s) andscript/cibuild(11.5s) all exit 0 with every check layer observed executing;script/cibuildfails fast in 0.630s on a planted lint error.make fmtleaves the tree unchanged. The deploy path was checked as a diff, not inferred:.gitea/,script/bootstrap,script/testand.dockerignoreare byte-identical tomainat7d7bec5. No prune of any kind was run; every invalidation was scoped to a single build through its own build argument.Independent re-review —
25b6c0aonly (407b0a0out of scope)Verdict: PASS —
merge-ready.Reviewed in an own fresh clone at
next=25b6c0a. CIsuccesson the head commit (1m23s);nextis a fast-forward ofmainat7d7bec5(2 commits, no conflicts).make fmtleaves the tree unchanged;make checkgreen (1.4s warm) with all three checks observed executing.The
--output type=cacheonlydeviation — attacked, holdsEvery property it could have broken was tested, not reasoned about:
{{ .ThisFieldDoesNotExistRev2E1 }}inthemes/loravega/layouts/index.html:script/lintexit 1 with hugo's owncan't evaluate field ThisFieldDoesNotExistRev2E1 in type *hugolib.pageState, attributed toDockerfile.lint:61. Guard fails closed undercacheonlytoo — both a bare argument-less build and--build-arg CHECK_EPOCH=""die onRUN [ -n "$CHECK_EPOCH" ] || exit 1, exit 1.script/lintinvocations on an unchanged tree, distinct epochs echoed, real hugo output and page table each time; the lint layer was neverCACHEDin any run.RUN script/bootstrapCACHEDthroughout. Noexporting/naming toline appears in any lint build — nothing is exported.script/bootstraplayer is shared between the two images; its evidence was on a warm cache, which does not test whether a non-exporting build populates the cache for a later exporting one. I invalidated the layer key by appending a comment toscript/bootstrap:script/lintthen paid a genuine cold bootstrap (#8 [4/7] RUN script/bootstrapDONE 51.0s, real apk/go output), and the immediately followingscript/dockercame back#8 [4/8] RUN script/bootstrapCACHED. The claim holds undercacheonly. Probe reverted.cacheonlyby the dangling-image leak. Measured here, the main image'sexporting to imagestep is 121.8s;cacheonlyremoves an export of that class from every lint run, which dwarfs the leak argument.Non-blocking finding — the BuildKit dependency is undocumented.
--outputis a buildx flag. Empirically, on this host (docker 29.7.2,buildx 0.36.1):A docker with no buildx plugin takes the same legacy-builder path, so this covers that case as well (removing the plugin from a shared host was not an option). This is not blocking: CI is green on
25b6c0a, andscript/cibuildrunsscript/lintfirst underset -eu, so a green run provesscript/lintexited 0 on the actual Gitea runner — the runner has buildx. And the failure mode is loud (exit 125, no lint claimed), never a false green. What is missing is a line saying so:script/check's header documents "needs a working docker daemon" and the cold-bootstrap cost,script/lint's header documents thecacheonlyrationale, but neither says the build now requires BuildKit/buildx. Acceptable: one sentence inscript/lint'scacheonlycomment naming buildx as a prerequisite, so theunknown flag: --outputfailure is diagnosable.Prior five findings — each confirmed fixed
script/fmt-checkback on the host: diff vs7d7bec5is comment-only,prettier@3.4.2/'**/*.md' '**/*.css' --tab-width 4 --prose-wrap alwaysbyte-identical,CHECK_EPOCHgeneration gone.Dockerfileruns both — observed executing in onescript/cibuild:#11 [7/8] RUN echo "check epoch: ..." && script/testand#12 [8/8] RUN script/fmt-check→All matched files use Prettier code style!DONE 1.7s. Both gate: a planted over-long line inREADME.mdfailed the hostscript/fmt-check(exit 1) and failedscript/dockeratDockerfile:56.Dockerfile.lintis single-stage; a whole-filedocker build -f Dockerfile.lint .with the arg is exactly whatscript/lintnow runs, and it genuinely lints (evidence above). No--targetanywhere.script/cibuildfails fast — with the template error planted it exited 1 in 2.0s with zerocheck epochlines in the log; the main image build never started.REPO_POLICIES.mddivergences (:92and:102-168) are stated in the commit body with the superseding upstream change linked.Deploy path — cannot be affected
.gitea/,script/bootstrap,script/test,.dockerignoreand theMakefileare byte-identical tomainat7d7bec5(checked as a diff).deploy.ymlrunsscript/bootstrapthenscript/testin an alpine container and touches no Dockerfile, nomake check, no docker build. Additionally verified at the output layer rather than inferred:hugo --minifyfrom7d7bec5and from25b6c0aproduce a byte-identicalpublic/tree (sha256 of every file). A merge changes nothing that gets published.Anomalies that pass anyway
Dockerfile:56RUN script/fmt-checkcarries no epoch of its own, unlike line 55. It is still uncacheable, because its parent layer's command string contains the per-invocation epoch and therefore yields a new layer ID every build. Confirmed rather than assumed: that step showedDONE, neverCACHED, in three independent builds. Correct as written; noting it because it looks asymmetric.README.md:96says "Build any image throughscript/cibuild,script/dockerorscript/lintonly" — undercacheonly,script/lintbuilds no image. Cosmetic, not worth a rework cycle.Checked and clean
Every comment in
Dockerfile,Dockerfile.lint,script/*andREADME.mddescribes what the code now does — no stale claim survives, including the--printPathWarningswording, which correctly says "reports" collisions rather than "fails on" them. Nomake checkclaim remains anywhere except where it is explaining its own absence. Commit message ends(closes #38); no attribution trailers and no vendor references anywhere in the diff or tree; inclusive terminology; naming and idiom consistent with the surrounding scripts; no scope creep (theTODO.mdFuture Steps reordering is the manager-directed fix for the defect carried in from407b0a0, and the new Next Step points at startable work, #34). The known--printPathWarningslimitation is correctly disclosed as pre-existing and tracked at #25 .Disclosures
403: user should be the owner of the repo), so I did not read CI output confirming the lint layer executed on the runner. I established it structurally instead:script/cibuildrunsscript/lintfirst underset -eu, every entrypoint mints a fresh epoch per invocation, and the status issuccessin 1m23s on25b6c0a. CI-side layer execution remains asserted by the author, not observed by me.DOCKER_BUILDKIT=0rather than by removing the plugin, since the host is shared. Both routes reach the same legacy builder that rejects--output.script/bootstraplayer for one build pair by editingscript/bootstrap; it was reverted, andlora.vegas:latestwas rebuilt from the clean tree so the host is left as found. No prune of any kind was run at any point; every invalidation was scoped to a single build. The review clone is clean; nothing was committed or pushed.Manager note. Re-review PASSED. Merging to
main.Merging now rather than accumulating, deliberately: the reviewer built
public/from7d7bec5and from25b6c0aand sha256'd every file — byte-identical. So this merge fires a real Cloudflare Pages deploy that publishes nothing new, which is the lowest-risk moment available to exercise a deploy path that has broken before, and it puts thestatic/_headersfrom #14 in front of production so its open "unverified until the next deploy" item can finally be closed. Deferring only makes the eventual merge larger and less inspectable.One gap accepted rather than reworked:
script/lintrequires buildx (DOCKER_BUILDKIT=0givesunknown flag: --output, exit 125). It fails loudly and never as a false green, and CI is green on25b6c0a, so it is a documentation gap, not a defect. Filed as #40 .