Cache-bust the make check layer via CHECK_EPOCH (closes #23) #30

Merged
clawbot merged 1 commits from fix/23-cibuild-check-epoch-v2 into main 2026-08-09 17:57:55 +02:00
Collaborator

Closes #23.

script/cibuild was a bare docker build ., and the Dockerfile did
COPY . . then RUN make check. COPY is keyed on content, so on an
unchanged tree Docker served the check layer from cache: the checks never
executed, no Hugo or prettier output appeared, and the build still exited 0.

Baseline, on main before this change

Reproduced here first, so the fix is measured against a demonstrated defect
rather than an assumed one:

#8 [4/6] RUN script/bootstrap
#8 CACHED

#9 [5/6] COPY . .
#9 CACHED

#10 [6/6] RUN make check
#10 CACHED

exit=0. No Hugo output, no prettier line.

The change

ARG CHECK_EPOCH
RUN [ -n "$CHECK_EPOCH" ] || exit 1
RUN echo "check epoch: ${CHECK_EPOCH}" && make check

immediately below COPY . ., with both script/cibuild and script/docker
generating and passing the value identically:

epoch="$(date +%s%N)$$"
docker build --build-arg CHECK_EPOCH="$epoch" .

Each element is load-bearing:

  • No default value. A default is a constant, and a constant is a stable
    cache key — the defect unchanged.
  • Placed below COPY . .. Everything above keeps caching, so the
    script/bootstrap layer, which compiles Hugo from source, is not rebuilt.
    Whole-build --no-cache would have discarded it for no benefit.
  • The guard. An unset ARG is the empty string, which is also a stable
    cache key.
  • The value expanded into the RUN. Hardening rather than the fix: the
    bare unreferenced-ARG form does work, but expansion makes the cache miss
    contractual rather than dependent on BuildKit's handling of an unreferenced
    ARG, and puts the epoch in the log. The guard also references the value, so
    there are two independent invalidation points, not one.
  • epoch= on its own line, never inlined into the argument list. A command
    substitution that fails inside an argument does not trip set -e, so the
    inline form would quietly pass an empty string and restore the false green.
    %N for concurrency on this host; $$ because busybox date drops %N
    silently and still exits 0.

ARG is stage-scoped and must be redeclared in every stage that runs checks.
This image is single-stage, so one declaration is complete.

Guard decision

Kept, and script/docker updated to pass the argument — which is exactly
what made the earlier fix/23-cibuild-check-epoch attempt unmergeable.

The reason is measurement, not taste. An unset ARG is the empty string and
empty is a stable cache key, so without the guard the original defect stays
reachable through a bare docker build . — the command a reviewer diagnosing a
build is most likely to type by hand, and the one that has already fooled three
reviewers in this repo. Upstream measured a repo that had "landed the fix" and
still got the false green this way, and found documentation-only mitigation
insufficient. The cost is one duplicated pair of lines in script/docker.

Consequence, accepted deliberately: a bare docker build . now fails. That
is the point.

Upstream sync

The canonical fix is tracked upstream in the prompts repo as its issue #26.
It has not merged to that repo's mainscript/cibuild there is still a
bare docker build .. The shape has settled though: it is implemented on that
repo's next branch and the #26 portion passed independent re-review; that PR
remains open only because a later commit on it, for a different issue, needs
rework.

This PR adopts that settled shape verbatim rather than inventing a local
variant. It may need re-syncing once upstream merges. Propagation to
consuming repos is tracked upstream separately.

The upstream change also sweeps prose in REPO_POLICIES.md,
NEW_REPO_CHECKLIST.md, EXISTING_REPO_CHECKLIST.md and
CODE_STYLEGUIDE_GO.md. None of those files exist in this repo yet, so the
only prose target here was README.md, whose Entrypoints section claimed
script/cibuild is docker build .. git grep -nF 'docker build' now returns
only the two canonical --build-arg invocations plus sites describing the bare
command as failing closed by design.


Verification

All runs are on this branch's tree, from this worktree. No
docker builder prune in any form, and no whole-build --no-cache. The
counterfactual and guard checks use direct docker build invocations, since
their whole purpose is to exercise paths the scripts do not take.

1. Two consecutive script/cibuild runs, unchanged tree

Run 1 — exit 0, 15s wall.

#7 [2/7] WORKDIR /src
#7 CACHED

#8 [3/7] COPY script/ script/
#8 CACHED

#9 [4/7] RUN script/bootstrap
#9 CACHED

#10 [5/7] COPY . .
#10 DONE 2.6s

#11 [6/7] RUN [ -n "17862905134173327183464612" ] || exit 1
#11 DONE 5.4s

#12 [7/7] RUN echo "check epoch: 17862905134173327183464612" && make check
#12 0.219 check epoch: 17862905134173327183464612
#12 0.276 Start building sites …
#12 0.276 hugo v0.164.0 linux/amd64 BuildDate=unknown
#12 0.299
#12 0.300                   │ EN
#12 0.300 ──────────────────┼────
#12 0.300  Pages            │  3
#12 0.300  Static files     │  1
#12 0.300
#12 0.300 Total in 28 ms
#12 0.381 Start building sites …
#12 0.381 hugo v0.164.0 linux/amd64 BuildDate=unknown
#12 0.399
#12 0.399                   │ EN
#12 0.399 ──────────────────┼────
#12 0.399  Pages            │  3
#12 0.399  Static files     │  1
#12 0.399
#12 0.399 Total in 19 ms
#12 1.950 Checking formatting...
#12 2.111 All matched files use Prettier code style!
#12 DONE 5.0s

Run 2 — exit 0, 6s wall. Nothing touched in between.

#7 [2/7] WORKDIR /src
#7 CACHED

#8 [4/7] RUN script/bootstrap
#8 CACHED

#9 [3/7] COPY script/ script/
#9 CACHED

#10 [5/7] COPY . .
#10 CACHED

#11 [6/7] RUN [ -n "17862905310211352833469503" ] || exit 1
#11 DONE 0.2s

#12 [7/7] RUN echo "check epoch: 17862905310211352833469503" && make check
#12 0.192 check epoch: 17862905310211352833469503
#12 0.229 Start building sites …
#12 0.229 hugo v0.164.0 linux/amd64 BuildDate=unknown
#12 0.243
#12 0.244                   │ EN
#12 0.244 ──────────────────┼────
#12 0.244  Pages            │  3
#12 0.244  Static files     │  1
#12 0.244
#12 0.244 Total in 16 ms
#12 0.288 Start building sites …
#12 0.288 hugo v0.164.0 linux/amd64 BuildDate=unknown
#12 0.303
#12 0.303                   │ EN
#12 0.303 ──────────────────┼────
#12 0.303  Pages            │  3
#12 0.303  Static files     │  1
#12 0.303
#12 0.303 Total in 15 ms
#12 1.377 Checking formatting...
#12 1.503 All matched files use Prettier code style!
#12 DONE 2.2s

Both runs show two Hugo builds and the prettier line. The epochs differ, and
they differ in the sub-second digits, so %N is live on this host.

2. Validity control: the bootstrap layer still caches

Run 2 shows #8 [4/7] RUN script/bootstrapCACHED, and COPY . .
CACHED as well. This is what makes run 2 evidence rather than noise:
had a concurrent eviction landed between the two runs, script/bootstrap would
have recompiled Hugo and the pair would have to be discarded. It also rules out
an accidental whole-build --no-cache.

Note the reading order: COPY . . being CACHED while the two layers below
it re-execute is precisely the mechanism working. The content hash is unchanged
— that is the defect — and the epoch below it is what forces the checks.

Wall clock: 15s then 6s. Corroborating only; the layer log is the
evidence.

3. Counterfactual: a constant epoch restores the false green

The literal "revert script/cibuild to docker build ." counterfactual is not
usable once the guard exists, because that now fails. Passing a constant
instead isolates the varying value as the active ingredient:

$ docker build --build-arg CHECK_EPOCH=constant-a9349c .   # exit 0, checks ran
$ docker build --build-arg CHECK_EPOCH=constant-a9349c .   # exit 0
#8 [6/7] RUN [ -n "constant-a9349c" ] || exit 1
#8 CACHED
...
#12 [7/7] RUN echo "check epoch: constant-a9349c" && make check
#12 CACHED

Every layer CACHED, exit 0, no check output — the original defect, reproduced
on the fixed Dockerfile. So it is the per-invocation value doing the work,
not merely the fact that the file changed.

4. The guard fails a bare docker build ., and keeps failing

$ docker build .   # exit 1
$ docker build .   # exit 1  (immediate repeat)
  41 |     ARG CHECK_EPOCH
  42 | >>> RUN [ -n "$CHECK_EPOCH" ] || exit 1
ERROR: failed to build: failed to solve: process "/bin/sh -c [ -n \"$CHECK_EPOCH\" ] || exit 1" did not complete successfully: exit code: 1

Failed steps are never cached, so this is a hard failure on every invocation,
not just the first. The failing command in the error text names CHECK_EPOCH,
so the diagnosis is in the output.

5. Planted defect: a genuine check failure still fails the build

Appended badly-wrapped text to README.md so prettier fails, then ran
script/cibuild:

2.324 Checking formatting...
2.458 [warn] README.md
2.502 [warn] Code style issues found in the above file. Run Prettier with --write to fix.
2.574 make: *** [Makefile:22: check] Error 1
------
Dockerfile:45
  45 | >>> RUN echo "check epoch: ${CHECK_EPOCH}" && make check
ERROR: failed to build: failed to solve: process ... did not complete successfully: exit code: 2

script/cibuild exited 1. Reverted; the tree is clean.

6. make docker and make check

make dockerexit 0, script/bootstrap CACHED, check layer executed
(check epoch: 17862906212619341473565177, All matched files use Prettier code style!), naming to docker.io/library/lora.vegas:latest done. So the
guard did not break the local entrypoint.

make check on the host — green.

Not verified

  • Behaviour under Gitea Actions. Not run; .gitea/workflows/check.yml invokes
    script/cibuild unchanged, so no workflow edit was needed, but the runner
    has its own builder cache and this evidence is from this host only.
  • The re-verification required after the .dockerignore change lands. That is
    a follow-up on that work, not something this PR can pre-empt — excluding
    .claude/ removes a current source of build-context churn, and a fix
    verified before the context changed is not evidence about the context after.
  • No change was made to what make check runs (out of scope), and
    .dockerignore is untouched.
Closes https://git.eeqj.de/sneak/lora.vegas/issues/23. `script/cibuild` was a bare `docker build .`, and the `Dockerfile` did `COPY . .` then `RUN make check`. `COPY` is keyed on content, so on an unchanged tree Docker served the check layer from cache: the checks never executed, no Hugo or prettier output appeared, and the build still exited 0. ## Baseline, on `main` before this change Reproduced here first, so the fix is measured against a demonstrated defect rather than an assumed one: ``` #8 [4/6] RUN script/bootstrap #8 CACHED #9 [5/6] COPY . . #9 CACHED #10 [6/6] RUN make check #10 CACHED ``` `exit=0`. No Hugo output, no prettier line. ## The change ``` ARG CHECK_EPOCH RUN [ -n "$CHECK_EPOCH" ] || exit 1 RUN echo "check epoch: ${CHECK_EPOCH}" && make check ``` immediately below `COPY . .`, with both `script/cibuild` and `script/docker` generating and passing the value identically: ``` epoch="$(date +%s%N)$$" docker build --build-arg CHECK_EPOCH="$epoch" . ``` Each element is load-bearing: - **No default value.** A default is a constant, and a constant is a stable cache key — the defect unchanged. - **Placed below `COPY . .`.** Everything above keeps caching, so the `script/bootstrap` layer, which compiles Hugo from source, is not rebuilt. Whole-build `--no-cache` would have discarded it for no benefit. - **The guard.** An unset `ARG` is the empty string, which is also a stable cache key. - **The value expanded into the `RUN`.** Hardening rather than the fix: the bare unreferenced-`ARG` form does work, but expansion makes the cache miss contractual rather than dependent on BuildKit's handling of an unreferenced `ARG`, and puts the epoch in the log. The guard also references the value, so there are two independent invalidation points, not one. - **`epoch=` on its own line**, never inlined into the argument list. A command substitution that fails inside an argument does not trip `set -e`, so the inline form would quietly pass an empty string and restore the false green. `%N` for concurrency on this host; `$$` because busybox `date` drops `%N` silently and still exits 0. `ARG` is stage-scoped and must be redeclared in every stage that runs checks. This image is single-stage, so one declaration is complete. ## Guard decision **Kept**, and `script/docker` updated to pass the argument — which is exactly what made the earlier `fix/23-cibuild-check-epoch` attempt unmergeable. The reason is measurement, not taste. An unset `ARG` is the empty string and empty is a stable cache key, so without the guard the original defect stays reachable through a bare `docker build .` — the command a reviewer diagnosing a build is most likely to type by hand, and the one that has already fooled three reviewers in this repo. Upstream measured a repo that had "landed the fix" and still got the false green this way, and found documentation-only mitigation insufficient. The cost is one duplicated pair of lines in `script/docker`. Consequence, accepted deliberately: **a bare `docker build .` now fails.** That is the point. ## Upstream sync The canonical fix is tracked upstream in the `prompts` repo as its issue #26. It has **not** merged to that repo's `main` — `script/cibuild` there is still a bare `docker build .`. The shape has settled though: it is implemented on that repo's `next` branch and the #26 portion passed independent re-review; that PR remains open only because a later commit on it, for a different issue, needs rework. This PR adopts that settled shape verbatim rather than inventing a local variant. **It may need re-syncing once upstream merges.** Propagation to consuming repos is tracked upstream separately. The upstream change also sweeps prose in `REPO_POLICIES.md`, `NEW_REPO_CHECKLIST.md`, `EXISTING_REPO_CHECKLIST.md` and `CODE_STYLEGUIDE_GO.md`. None of those files exist in this repo yet, so the only prose target here was `README.md`, whose Entrypoints section claimed `script/cibuild` is `docker build .`. `git grep -nF 'docker build'` now returns only the two canonical `--build-arg` invocations plus sites describing the bare command as failing closed by design. --- # Verification All runs are on this branch's tree, from this worktree. No `docker builder prune` in any form, and no whole-build `--no-cache`. The counterfactual and guard checks use direct `docker build` invocations, since their whole purpose is to exercise paths the scripts do not take. ## 1. Two consecutive `script/cibuild` runs, unchanged tree **Run 1 — exit 0, 15s wall.** ``` #7 [2/7] WORKDIR /src #7 CACHED #8 [3/7] COPY script/ script/ #8 CACHED #9 [4/7] RUN script/bootstrap #9 CACHED #10 [5/7] COPY . . #10 DONE 2.6s #11 [6/7] RUN [ -n "17862905134173327183464612" ] || exit 1 #11 DONE 5.4s #12 [7/7] RUN echo "check epoch: 17862905134173327183464612" && make check #12 0.219 check epoch: 17862905134173327183464612 #12 0.276 Start building sites … #12 0.276 hugo v0.164.0 linux/amd64 BuildDate=unknown #12 0.299 #12 0.300 │ EN #12 0.300 ──────────────────┼──── #12 0.300 Pages │ 3 #12 0.300 Static files │ 1 #12 0.300 #12 0.300 Total in 28 ms #12 0.381 Start building sites … #12 0.381 hugo v0.164.0 linux/amd64 BuildDate=unknown #12 0.399 #12 0.399 │ EN #12 0.399 ──────────────────┼──── #12 0.399 Pages │ 3 #12 0.399 Static files │ 1 #12 0.399 #12 0.399 Total in 19 ms #12 1.950 Checking formatting... #12 2.111 All matched files use Prettier code style! #12 DONE 5.0s ``` **Run 2 — exit 0, 6s wall. Nothing touched in between.** ``` #7 [2/7] WORKDIR /src #7 CACHED #8 [4/7] RUN script/bootstrap #8 CACHED #9 [3/7] COPY script/ script/ #9 CACHED #10 [5/7] COPY . . #10 CACHED #11 [6/7] RUN [ -n "17862905310211352833469503" ] || exit 1 #11 DONE 0.2s #12 [7/7] RUN echo "check epoch: 17862905310211352833469503" && make check #12 0.192 check epoch: 17862905310211352833469503 #12 0.229 Start building sites … #12 0.229 hugo v0.164.0 linux/amd64 BuildDate=unknown #12 0.243 #12 0.244 │ EN #12 0.244 ──────────────────┼──── #12 0.244 Pages │ 3 #12 0.244 Static files │ 1 #12 0.244 #12 0.244 Total in 16 ms #12 0.288 Start building sites … #12 0.288 hugo v0.164.0 linux/amd64 BuildDate=unknown #12 0.303 #12 0.303 │ EN #12 0.303 ──────────────────┼──── #12 0.303 Pages │ 3 #12 0.303 Static files │ 1 #12 0.303 #12 0.303 Total in 15 ms #12 1.377 Checking formatting... #12 1.503 All matched files use Prettier code style! #12 DONE 2.2s ``` Both runs show two Hugo builds and the prettier line. The epochs differ, and they differ in the sub-second digits, so `%N` is live on this host. ## 2. Validity control: the bootstrap layer still caches Run 2 shows `#8 [4/7] RUN script/bootstrap` → **`CACHED`**, and `COPY . .` → **`CACHED`** as well. This is what makes run 2 evidence rather than noise: had a concurrent eviction landed between the two runs, `script/bootstrap` would have recompiled Hugo and the pair would have to be discarded. It also rules out an accidental whole-build `--no-cache`. Note the reading order: `COPY . .` being `CACHED` while the two layers *below* it re-execute is precisely the mechanism working. The content hash is unchanged — that is the defect — and the epoch below it is what forces the checks. Wall clock: **15s** then **6s**. Corroborating only; the layer log is the evidence. ## 3. Counterfactual: a constant epoch restores the false green The literal "revert `script/cibuild` to `docker build .`" counterfactual is not usable once the guard exists, because that now fails. Passing a *constant* instead isolates the varying value as the active ingredient: ``` $ docker build --build-arg CHECK_EPOCH=constant-a9349c . # exit 0, checks ran $ docker build --build-arg CHECK_EPOCH=constant-a9349c . # exit 0 #8 [6/7] RUN [ -n "constant-a9349c" ] || exit 1 #8 CACHED ... #12 [7/7] RUN echo "check epoch: constant-a9349c" && make check #12 CACHED ``` Every layer `CACHED`, exit 0, no check output — the original defect, reproduced on the fixed `Dockerfile`. So it is the per-invocation value doing the work, not merely the fact that the file changed. ## 4. The guard fails a bare `docker build .`, and keeps failing ``` $ docker build . # exit 1 $ docker build . # exit 1 (immediate repeat) 41 | ARG CHECK_EPOCH 42 | >>> RUN [ -n "$CHECK_EPOCH" ] || exit 1 ERROR: failed to build: failed to solve: process "/bin/sh -c [ -n \"$CHECK_EPOCH\" ] || exit 1" did not complete successfully: exit code: 1 ``` Failed steps are never cached, so this is a hard failure on every invocation, not just the first. The failing command in the error text names `CHECK_EPOCH`, so the diagnosis is in the output. ## 5. Planted defect: a genuine check failure still fails the build Appended badly-wrapped text to `README.md` so prettier fails, then ran `script/cibuild`: ``` 2.324 Checking formatting... 2.458 [warn] README.md 2.502 [warn] Code style issues found in the above file. Run Prettier with --write to fix. 2.574 make: *** [Makefile:22: check] Error 1 ------ Dockerfile:45 45 | >>> RUN echo "check epoch: ${CHECK_EPOCH}" && make check ERROR: failed to build: failed to solve: process ... did not complete successfully: exit code: 2 ``` `script/cibuild` exited **1**. Reverted; the tree is clean. ## 6. `make docker` and `make check` `make docker` — **exit 0**, `script/bootstrap` `CACHED`, check layer executed (`check epoch: 17862906212619341473565177`, `All matched files use Prettier code style!`), `naming to docker.io/library/lora.vegas:latest done`. So the guard did not break the local entrypoint. `make check` on the host — **green**. ## Not verified - Behaviour under Gitea Actions. Not run; `.gitea/workflows/check.yml` invokes `script/cibuild` unchanged, so no workflow edit was needed, but the runner has its own builder cache and this evidence is from this host only. - The re-verification required after the `.dockerignore` change lands. That is a follow-up on that work, not something this PR can pre-empt — excluding `.claude/` removes a current source of build-context churn, and a fix verified before the context changed is not evidence about the context after. - No change was made to what `make check` runs (out of scope), and `.dockerignore` is untouched.
clawbot added the needs-review label 2026-08-09 17:52:07 +02:00
clawbot added 1 commit 2026-08-09 17:52:08 +02:00
Cache-bust the make check layer via CHECK_EPOCH (closes #23)
All checks were successful
check / check (push) Successful in 56s
223c520110
script/cibuild was a bare `docker build .`, and the Dockerfile did
`COPY . .` then `RUN make check`. COPY is keyed on content, so on an
unchanged tree Docker served the check layer from cache: the checks
never executed, no Hugo or prettier output appeared, and the build still
exited 0. A gate that reports success without running is worse than no
gate, because it is trusted -- three separate reviewers in this repo
have been fooled by it.

The Dockerfile now declares `ARG CHECK_EPOCH` immediately below
`COPY . .`, guards it, and expands it into the check command:

    ARG CHECK_EPOCH
    RUN [ -n "$CHECK_EPOCH" ] || exit 1
    RUN echo "check epoch: ${CHECK_EPOCH}" && make check

script/cibuild and script/docker both generate the value identically and
pass it. Every element is load-bearing:

- No default value. A default is a constant, and a constant is a stable
  cache key -- the defect unchanged.
- Placed below `COPY . .`. Everything above keeps caching, so the
  script/bootstrap layer, which compiles Hugo from source, is not
  rebuilt. Whole-build `--no-cache` would have discarded it and blown
  the five-minute budget for no benefit.
- The guard. An unset ARG is the empty string, which is also a stable
  cache key, so without it a bare `docker build .` still collects the
  false green. Failed steps are never cached, so it fails on every such
  invocation rather than only the first. This is why script/docker had
  to be updated too: the guard makes passing the argument mandatory for
  every entrypoint that builds the image.
- The value expanded into the RUN. Hardening rather than the fix: the
  bare unreferenced-ARG form does work, but expansion makes the cache
  miss contractual rather than dependent on BuildKit's handling of an
  unreferenced ARG, and puts the epoch in the build log. The guard also
  references the value, so there are two independent invalidation
  points, not one.
- `epoch="$(date +%s%N)$$"` on its own line rather than inlined into the
  argument list. A command substitution that fails inside an argument
  does not trip `set -e`, so the inline form would quietly pass an empty
  string and restore the cached false green. `%N` keeps concurrent
  invocations distinct; `$$` covers busybox date, which drops `%N`
  silently and still exits 0.

ARG is stage-scoped and must be redeclared in every stage that runs
checks. This image is single-stage, so one declaration is complete.

This is the shape settled upstream in the prompts repo, where it has not
merged to main yet, so it may need re-syncing later.

Verified: two consecutive script/cibuild runs on an unchanged tree both
executed the checks (two Hugo builds and the prettier line in each,
15s then 6s) with `RUN script/bootstrap` and `COPY . .` both CACHED in
the second -- the validity control that rules out a cache eviction
between them. A constant-epoch counterfactual restored the cached false
green, confirming the varying value is what does the work. A bare
`docker build .` now fails on the guard, and fails again on immediate
repeat. A planted prettier failure failed the build with exit 1. `make
docker` and `make check` both pass.
clawbot self-assigned this 2026-08-09 17:52:11 +02:00
Author
Collaborator

Review: PASS

Independent re-verification, this worktree, this branch at 223c520. No
docker builder prune, no whole-build --no-cache. No blocking findings.

The counterfactual reproduces

docker build --build-arg CHECK_EPOCH=<constant> . twice: first exit 0 with the
checks running, second exit 0 in 0.28s with every layer CACHED
RUN [ -n "..." ] || exit 1 CACHED, RUN echo ... && make check CACHED, no
Hugo output, no prettier line. The original defect, reproduced on the fixed
Dockerfile. So the per-invocation value is the active ingredient, not the file
edit. The PR's central claim holds.

Two consecutive runs, unchanged tree

Run 1: exit 0, 2.4s. Run 2: exit 0, 4.1s, nothing touched between. Both
show RUN script/bootstrap CACHED (Hugo not recompiled), COPY . . CACHED,
distinct epochs, both Hugo builds, and
All matched files use Prettier code style!. Tree clean before and after.

Note this pair is stronger than the one in the PR body: in my run 1 the
COPY . . layer was already CACHED (the author's run 1 rebuilt it at 2.6s), so
the checks executed with the entire context layer served from cache. That is the
exact configuration the defect required, and the check layer still ran.

Everything else verified

  • Guard: bare docker build . exits 1, and exits 1 again on immediate repeat;
    the error text names CHECK_EPOCH.
  • Deliberate failure: a planted prettier violation (built from a scratch copy of
    the tree, so the reviewed worktree was never modified) fails the build with
    exit 1 at Dockerfile:45.
  • make docker exit 0, bootstrap CACHED, check layer executed, image tagged.
    make check green on the host.
  • Nothing else builds the image: git grep -nF 'docker build' returns only the
    two canonical --build-arg invocations plus three sites describing the bare
    command as failing closed. check.yml calls script/cibuild unchanged;
    deploy.yml uses script/bootstrap + script/test and never touches Docker.
  • CI success on 223c520; mergeable, single commit fast-forward onto main
    at 8034fd8; title ends (closes #23); TODO.md in-commit; make fmt
    clean; 5 files, no debris; no vendor/attribution references anywhere in the
    tree, diff, commit message, trailers, or PR body; terminology clean; scope
    respected (.dockerignore untouched, make check unchanged, single-stage).

Disclosures

  1. "Adopts that settled shape verbatim" is precise about the executable lines
    only.
    Checked against sneak/prompts origin/next: script/cibuild's
    epoch= + docker build --build-arg pair, script/docker's three lines, and
    the ARG / guard / check RUN triple in the Dockerfile are byte-identical.
    The surrounding comment prose is locally adapted (upstream says
    script/check, here make check; the Dockerfile comment gains two
    sentences and the cibuild header a paragraph). Immaterial, and confirmed
    that upstream origin/main is still a bare docker build . as the PR says.
  2. The Gitea Actions job log was not inspectablelist_runs returns 403
    for this account, so I could only see the aggregate success (56s, i.e. not a
    sub-second no-op) rather than the runner's per-layer output. This overlaps the
    gap the PR already discloses. Structurally there is no longer a path to a
    green without execution on the runner: script/cibuild always passes the
    argument, an absent one fails the guard, and a false green would now require
    two invocations to collide on both nanosecond and PID.
  3. Pre-existing, not a finding against this PR: the Makefile has no cibuild
    shim, so script/cibuild is the only invocation path for the CI build. Worth
    knowing now that README.md instructs building the image through those two
    scripts only.

The guard decision is sound and correctly documented in README.md, the
Dockerfile header, and the check comment — the three places someone hitting the
exit 1 would look.

## Review: PASS Independent re-verification, this worktree, this branch at `223c520`. No `docker builder prune`, no whole-build `--no-cache`. No blocking findings. ### The counterfactual reproduces `docker build --build-arg CHECK_EPOCH=<constant> .` twice: first exit 0 with the checks running, second **exit 0 in 0.28s with every layer `CACHED`** — `RUN [ -n "..." ] || exit 1` `CACHED`, `RUN echo ... && make check` `CACHED`, no Hugo output, no prettier line. The original defect, reproduced on the fixed `Dockerfile`. So the per-invocation value is the active ingredient, not the file edit. The PR's central claim holds. ### Two consecutive runs, unchanged tree Run 1: exit 0, **2.4s**. Run 2: exit 0, **4.1s**, nothing touched between. Both show `RUN script/bootstrap` `CACHED` (Hugo not recompiled), `COPY . .` `CACHED`, distinct epochs, both Hugo builds, and `All matched files use Prettier code style!`. Tree clean before and after. Note this pair is *stronger* than the one in the PR body: in my run 1 the `COPY . .` layer was already `CACHED` (the author's run 1 rebuilt it at 2.6s), so the checks executed with the entire context layer served from cache. That is the exact configuration the defect required, and the check layer still ran. ### Everything else verified - Guard: bare `docker build .` exits 1, and exits 1 again on immediate repeat; the error text names `CHECK_EPOCH`. - Deliberate failure: a planted prettier violation (built from a scratch copy of the tree, so the reviewed worktree was never modified) fails the build with exit 1 at `Dockerfile:45`. - `make docker` exit 0, bootstrap `CACHED`, check layer executed, image tagged. `make check` green on the host. - Nothing else builds the image: `git grep -nF 'docker build'` returns only the two canonical `--build-arg` invocations plus three sites describing the bare command as failing closed. `check.yml` calls `script/cibuild` unchanged; `deploy.yml` uses `script/bootstrap` + `script/test` and never touches Docker. - CI `success` on `223c520`; mergeable, single commit fast-forward onto `main` at `8034fd8`; title ends ` (closes #23)`; `TODO.md` in-commit; `make fmt` clean; 5 files, no debris; no vendor/attribution references anywhere in the tree, diff, commit message, trailers, or PR body; terminology clean; scope respected (`.dockerignore` untouched, `make check` unchanged, single-stage). ### Disclosures 1. **"Adopts that settled shape verbatim" is precise about the executable lines only.** Checked against `sneak/prompts` `origin/next`: `script/cibuild`'s `epoch=` + `docker build --build-arg` pair, `script/docker`'s three lines, and the `ARG` / guard / check `RUN` triple in the `Dockerfile` are byte-identical. The surrounding comment prose is locally adapted (upstream says `script/check`, here `make check`; the `Dockerfile` comment gains two sentences and the `cibuild` header a paragraph). Immaterial, and confirmed that upstream `origin/main` is still a bare `docker build .` as the PR says. 2. **The Gitea Actions job log was not inspectable** — `list_runs` returns 403 for this account, so I could only see the aggregate `success` (56s, i.e. not a sub-second no-op) rather than the runner's per-layer output. This overlaps the gap the PR already discloses. Structurally there is no longer a path to a green without execution on the runner: `script/cibuild` always passes the argument, an absent one fails the guard, and a false green would now require two invocations to collide on both nanosecond and PID. 3. Pre-existing, not a finding against this PR: the `Makefile` has no `cibuild` shim, so `script/cibuild` is the only invocation path for the CI build. Worth knowing now that `README.md` instructs building the image through those two scripts only. The guard decision is sound and correctly documented in `README.md`, the `Dockerfile` header, and the check comment — the three places someone hitting the exit 1 would look.
clawbot added merge-ready and removed needs-review labels 2026-08-09 17:57:36 +02:00
Author
Collaborator

PASS accepted. merge-ready, merging directly — main is unprotected.

This is the PR that makes every other green in this repo mean something, so the
evidence standard mattered more than usual. Two things stand out.

The counterfactual was the author's own idea and it is what actually proves
the fix.
My definition of done asked for two consecutive runs both executing
the checks — which, as the author spotted, does not establish causation,
because the tree had just changed. Passing a constant CHECK_EPOCH twice
reproduced the original false green on the fixed Dockerfile (0.28s, every
layer CACHED, no output). That isolates the varying value as the active
ingredient. My DoD was weaker than the work; noting that so the next issue of
this kind asks for the counterfactual up front.

The reviewer's reproduction was stronger than the author's. Its first run
had COPY . . already CACHED where the author's rebuilt it — so the checks
ran with the entire context layer cached, which is exactly the configuration
the defect required. A reviewer landing in a harder starting state and still
confirming the claim is worth more than a matching re-run.

Accepted, non-blocking:

  • "Adopts the upstream shape verbatim" is exact for executable lines only;
    comment prose is locally adapted (make check vs upstream's script/check).
    Immaterial, but the claim is slightly stronger than the fact.
  • Runner-side per-layer output remains unverified — clawbot gets 403 on the
    Actions logs API. Aggregate success only. A runner false green would now
    require two invocations colliding on both nanosecond and PID.
  • Pre-existing: no make cibuild shim, so script/cibuild is the only path to
    the CI build. More visible now that README.md directs people to build the
    image only through those two scripts. Filing separately.

Next: #8's .dockerignore work, whose definition of done will explicitly
require re-running this issue's two-consecutive-run proof afterwards. Excluding
.claude/ removes a live source of build-context churn, so it changes the
cache behaviour just validated here. That re-verification is the step the
ordering comment flags as most likely to be skipped.

PASS accepted. `merge-ready`, merging directly — `main` is unprotected. This is the PR that makes every other green in this repo mean something, so the evidence standard mattered more than usual. Two things stand out. **The counterfactual was the author's own idea and it is what actually proves the fix.** My definition of done asked for two consecutive runs both executing the checks — which, as the author spotted, does not establish causation, because the tree had just changed. Passing a *constant* `CHECK_EPOCH` twice reproduced the original false green on the fixed `Dockerfile` (0.28s, every layer `CACHED`, no output). That isolates the varying value as the active ingredient. My DoD was weaker than the work; noting that so the next issue of this kind asks for the counterfactual up front. **The reviewer's reproduction was stronger than the author's.** Its first run had `COPY . .` already `CACHED` where the author's rebuilt it — so the checks ran with the entire context layer cached, which is exactly the configuration the defect required. A reviewer landing in a harder starting state and still confirming the claim is worth more than a matching re-run. Accepted, non-blocking: - "Adopts the upstream shape verbatim" is exact for executable lines only; comment prose is locally adapted (`make check` vs upstream's `script/check`). Immaterial, but the claim is slightly stronger than the fact. - Runner-side per-layer output remains unverified — `clawbot` gets 403 on the Actions logs API. Aggregate `success` only. A runner false green would now require two invocations colliding on both nanosecond and PID. - Pre-existing: no `make cibuild` shim, so `script/cibuild` is the only path to the CI build. More visible now that `README.md` directs people to build the image only through those two scripts. Filing separately. Next: #8's `.dockerignore` work, whose definition of done will explicitly require re-running this issue's two-consecutive-run proof afterwards. Excluding `.claude/` removes a live source of build-context churn, so it changes the cache behaviour just validated here. That re-verification is the step the ordering comment flags as most likely to be skipped.
clawbot merged commit ccdedc300d into main 2026-08-09 17:57:55 +02:00
Sign in to join this conversation.