make check runs prettier --check . twice #29

Open
opened 2026-08-09 16:53:53 +02:00 by clawbot · 3 comments
Collaborator

Problem

script/check runs script/test, script/lint, script/fmt-check. But script/lint already
runs prettier --check . in addition to eslint, so make check executes the same prettier pass
twice. Visible in the output as two consecutive "All matched files use Prettier code style!"
blocks.

Pre-existing; spotted during review of #28 and deliberately left out of that PR's scope.

Definition of done

prettier --check runs once per make check. Either script/lint drops it and stays eslint-only,
or script/check stops calling script/fmt-check separately — pick whichever keeps
make lint and make fmt-check meaningful as standalone targets, since REPO_POLICIES.md
requires both to exist and do what their names say.

make check, make lint and make fmt-check all still behave correctly when invoked on their
own. make check green.

Not a 1.0.0 blocker

Wasted work, not wrong behaviour.

## Problem `script/check` runs `script/test`, `script/lint`, `script/fmt-check`. But `script/lint` already runs `prettier --check .` in addition to eslint, so `make check` executes the same prettier pass twice. Visible in the output as two consecutive "All matched files use Prettier code style!" blocks. Pre-existing; spotted during review of #28 and deliberately left out of that PR's scope. ## Definition of done `prettier --check` runs once per `make check`. Either `script/lint` drops it and stays eslint-only, or `script/check` stops calling `script/fmt-check` separately — pick whichever keeps `make lint` and `make fmt-check` meaningful as standalone targets, since `REPO_POLICIES.md` requires both to exist and do what their names say. `make check`, `make lint` and `make fmt-check` all still behave correctly when invoked on their own. `make check` green. ## Not a 1.0.0 blocker Wasted work, not wrong behaviour.
clawbot self-assigned this 2026-08-09 16:53:53 +02:00
Author
Collaborator

The ground has moved since this was filed: #30 has landed on next, so script/lint is now a docker build of Dockerfile.lint, and that container is the authoritative prettier verdict. script/fmt-check still runs prettier on the host. So the duplicate is no longer two identical host passes — it is one host pass and one container pass.

That resolves the choice this issue left open. Drop the script/fmt-check call from script/check (and check script/precommit for the same duplication) rather than removing prettier from script/lint: the container has to keep checking formatting, because a successful Dockerfile.lint build is what CI treats as proof. make fmt-check stays as a standalone target, which is what REPO_POLICIES.md requires.

Requirements:

  • Exactly one prettier invocation per make check, and a test that asserts it rather than a claim that it is so — this repo has a standing habit of guarantees nothing enforces.
  • make lint, make fmt-check and make check must each still do what their name says when invoked alone.
  • Confirm the host and container prettier verdicts cannot disagree. Review of #31 established they currently cannot: prettier is pinned at 3.8.1 under --frozen-lockfile, it reads .gitignore by default, and .dockerignore mirrors it. Do not break that alignment; if your change makes make fmt-check the only host-side formatting check, say what now catches a formatting problem before commit.
  • script/cibuild still green and shown to have actually executed, not served from cache.
The ground has moved since this was filed: https://git.eeqj.de/sneak/quak/issues/30 has landed on `next`, so `script/lint` is now a `docker build` of `Dockerfile.lint`, and that container is the authoritative prettier verdict. `script/fmt-check` still runs prettier on the host. So the duplicate is no longer two identical host passes — it is one host pass and one container pass. That resolves the choice this issue left open. Drop the `script/fmt-check` call from `script/check` (and check `script/precommit` for the same duplication) rather than removing prettier from `script/lint`: the container has to keep checking formatting, because a successful `Dockerfile.lint` build is what CI treats as proof. `make fmt-check` stays as a standalone target, which is what `REPO_POLICIES.md` requires. Requirements: - Exactly one prettier invocation per `make check`, and a test that asserts it rather than a claim that it is so — this repo has a standing habit of guarantees nothing enforces. - `make lint`, `make fmt-check` and `make check` must each still do what their name says when invoked alone. - Confirm the host and container prettier verdicts cannot disagree. Review of https://git.eeqj.de/sneak/quak/pulls/31 established they currently cannot: prettier is pinned at 3.8.1 under `--frozen-lockfile`, it reads `.gitignore` by default, and `.dockerignore` mirrors it. Do not break that alignment; if your change makes `make fmt-check` the only host-side formatting check, say what now catches a formatting problem before commit. - `script/cibuild` still green and shown to have actually executed, not served from cache.
Author
Collaborator

Implementation plan, following the ruling in the comment above (drop the host pass, keep the container one).

1. script/check — remove the "$SCRIPT_DIR/fmt-check" line, leaving script/test then script/lint. Update the header comment to say why: script/lint builds Dockerfile.lint, which runs prettier --check . as a build step, so a separate host pass is a second, weaker verdict of the same thing.

2. script/precommit — same duplication, same fix. It currently runs script/lint then script/fmt-check; drop the second. The hook keeps catching formatting problems, because script/lint is the container prettier run — so the pre-commit hook still does exactly what it did, minus one redundant pass. make fmt-check survives as a standalone target (REPO_POLICIES.md requires it), it just stops being wired into script/check and script/precommit.

3. A test that enforces it, test/packaging/lint-once.test.ts. Not a prose claim: it statically resolves the invocation graph rooted at script/check and counts prettier invocations. The resolver follows "$SCRIPT_DIR/<name>" and script/<name> edges, make <target> edges through the Makefile shims, yarn run <name> edges through package.json scripts, and docker build -f Dockerfile.lint into that file's RUN steps — comments stripped, since they name the very commands under assertion. Assertions:

  • exactly one prettier invocation reachable from script/check, and exactly one from script/precommit;
  • script/fmt-check and script/lint each still invoke prettier exactly once when invoked alone, so neither becomes a no-op that trivially satisfies the count;
  • the walk actually reached Dockerfile.lint, so a restructure that defeats the resolver fails the test instead of passing with a count of zero.

I will mutate the assertion (re-add the fmt-check call to script/check) and confirm the test goes red before trusting it.

4. Alignment check. Host and container prettier verdicts must stay unable to disagree, per review of #31: prettier pinned at 3.8.1, installed under --frozen-lockfile, reading .gitignore by default, with .dockerignore mirroring .gitignore and deliberately keeping .gitignore in the context. Nothing in this change touches any of those four; I will re-verify and record it rather than assert it.

5. README — the Entrypoints entries for script/check and script/precommit, the pre-commit item in the development workflow, and the required-checks bullet all currently describe fmt-check as part of both. Corrected to match.

6. TODO.md — a Completed Steps entry; Next Step left alone (it is the unrelated README API reference).

Verification: make fmt, then make lint, make fmt-check and make check each invoked on their own, then script/cibuild with timing and per-layer output shown so a cached green cannot be mistaken for an executed one. Prettier invocation counts recorded before and after.

Implementation plan, following the ruling in the comment above (drop the host pass, keep the container one). **1. `script/check`** — remove the `"$SCRIPT_DIR/fmt-check"` line, leaving `script/test` then `script/lint`. Update the header comment to say why: `script/lint` builds `Dockerfile.lint`, which runs `prettier --check .` as a build step, so a separate host pass is a second, weaker verdict of the same thing. **2. `script/precommit`** — same duplication, same fix. It currently runs `script/lint` then `script/fmt-check`; drop the second. The hook keeps catching formatting problems, because `script/lint` is the container prettier run — so the pre-commit hook still does exactly what it did, minus one redundant pass. `make fmt-check` survives as a standalone target (`REPO_POLICIES.md` requires it), it just stops being wired into `script/check` and `script/precommit`. **3. A test that enforces it**, `test/packaging/lint-once.test.ts`. Not a prose claim: it statically resolves the invocation graph rooted at `script/check` and counts prettier invocations. The resolver follows `"$SCRIPT_DIR/<name>"` and `script/<name>` edges, `make <target>` edges through the Makefile shims, `yarn run <name>` edges through `package.json` scripts, and `docker build -f Dockerfile.lint` into that file's `RUN` steps — comments stripped, since they name the very commands under assertion. Assertions: - exactly one prettier invocation reachable from `script/check`, and exactly one from `script/precommit`; - `script/fmt-check` and `script/lint` each still invoke prettier exactly once when invoked alone, so neither becomes a no-op that trivially satisfies the count; - the walk actually reached `Dockerfile.lint`, so a restructure that defeats the resolver fails the test instead of passing with a count of zero. I will mutate the assertion (re-add the `fmt-check` call to `script/check`) and confirm the test goes red before trusting it. **4. Alignment check.** Host and container prettier verdicts must stay unable to disagree, per review of https://git.eeqj.de/sneak/quak/pulls/31: prettier pinned at 3.8.1, installed under `--frozen-lockfile`, reading `.gitignore` by default, with `.dockerignore` mirroring `.gitignore` and deliberately keeping `.gitignore` in the context. Nothing in this change touches any of those four; I will re-verify and record it rather than assert it. **5. README** — the Entrypoints entries for `script/check` and `script/precommit`, the pre-commit item in the development workflow, and the required-checks bullet all currently describe `fmt-check` as part of both. Corrected to match. **6. `TODO.md`** — a Completed Steps entry; Next Step left alone (it is the unrelated README API reference). Verification: `make fmt`, then `make lint`, `make fmt-check` and `make check` each invoked on their own, then `script/cibuild` with timing and per-layer output shown so a cached green cannot be mistaken for an executed one. Prettier invocation counts recorded before and after.
Author
Collaborator

Done in commit a73f0ab on next, which is carried by the open PR #31 (comment there has the full evidence).

What was built. script/check no longer calls script/fmt-check — it runs script/test then script/lint, and script/lint is the build of Dockerfile.lint, which runs prettier --check . as a build step. script/precommit had the same duplication and got the same fix. script/fmt-check is unchanged and still wired to make fmt-check; it is now a standalone entrypoint rather than a step inside two other scripts.

test/packaging/lint-once.test.ts enforces the count instead of promising it. It walks the invocation graph from an entrypoint, following "$SCRIPT_DIR/<name>" and script/<name> edges, make <target> through the Makefile shims, yarn run <name> through the package.json scripts, and docker build -f <file> into that Dockerfile's RUN steps, with comments stripped. It asserts one prettier invocation per make check and per script/precommit, one each for make lint and make fmt-check alone so neither can satisfy the count by becoming a no-op, and which nodes the walk reached so a resolver that stops working fails loudly instead of counting zero.

How it was verified.

  • Prettier invocations per make check, counted in the output of a real run: 2 before (one container, one host), 1 after. Both runs exit 0.
  • The test was mutated three ways and failed each time: re-adding script/fmt-check to script/check (expected 2 to be 1), deleting the prettier step from Dockerfile.lint (five failures), and gutting script/fmt-check (still checks formatting under make fmt-check). It also caught a genuine bug in its own first draft, where a slice argument mistake made every make-rooted walk return zero — the reachability assertions failed rather than the count silently passing.
  • Standalone behaviour: make fmt-check exit 0 in 2s on the host; make lint exit 0 in 74s with eslint and prettier both executing in the container, not cached; make check exit 0 in 18s, 258 tests passed.
  • script/cibuild exit 0 in 19s, with CACHED only on the dependency layers above the epoch guards and every layer below them showing a real duration.

On the one point this issue asked to be stated explicitly. make fmt-check is now the only host-side formatting check, but the thing that catches a formatting problem before a commit lands is unchanged: the pre-commit hook runs script/precommit, which runs script/lint, which is the container prettier run. It would have failed there before ever reaching script/fmt-check anyway. The hook lost a redundant pass, not a capability.

Host and container verdicts still cannot disagree, re-verified and now asserted by the test: prettier pinned at an exact 3.8.1, installed under --frozen-lockfile in both places, and .gitignore still in the build context because prettier reads it as a default ignore file. Nothing in this change touches any of the three.

Done in commit `a73f0ab` on `next`, which is carried by the open PR https://git.eeqj.de/sneak/quak/pulls/31 (comment there has the full evidence). **What was built.** `script/check` no longer calls `script/fmt-check` — it runs `script/test` then `script/lint`, and `script/lint` is the build of `Dockerfile.lint`, which runs `prettier --check .` as a build step. `script/precommit` had the same duplication and got the same fix. `script/fmt-check` is unchanged and still wired to `make fmt-check`; it is now a standalone entrypoint rather than a step inside two other scripts. `test/packaging/lint-once.test.ts` enforces the count instead of promising it. It walks the invocation graph from an entrypoint, following `"$SCRIPT_DIR/<name>"` and `script/<name>` edges, `make <target>` through the Makefile shims, `yarn run <name>` through the `package.json` scripts, and `docker build -f <file>` into that Dockerfile's `RUN` steps, with comments stripped. It asserts one prettier invocation per `make check` and per `script/precommit`, one each for `make lint` and `make fmt-check` alone so neither can satisfy the count by becoming a no-op, and which nodes the walk reached so a resolver that stops working fails loudly instead of counting zero. **How it was verified.** - Prettier invocations per `make check`, counted in the output of a real run: **2 before** (one container, one host), **1 after**. Both runs exit 0. - The test was mutated three ways and failed each time: re-adding `script/fmt-check` to `script/check` (`expected 2 to be 1`), deleting the prettier step from `Dockerfile.lint` (five failures), and gutting `script/fmt-check` (`still checks formatting under make fmt-check`). It also caught a genuine bug in its own first draft, where a `slice` argument mistake made every `make`-rooted walk return zero — the reachability assertions failed rather than the count silently passing. - Standalone behaviour: `make fmt-check` exit 0 in 2s on the host; `make lint` exit 0 in 74s with eslint and prettier both executing in the container, not cached; `make check` exit 0 in 18s, 258 tests passed. - `script/cibuild` exit 0 in 19s, with `CACHED` only on the dependency layers above the epoch guards and every layer below them showing a real duration. **On the one point this issue asked to be stated explicitly.** `make fmt-check` is now the only host-side formatting check, but the thing that catches a formatting problem before a commit lands is unchanged: the pre-commit hook runs `script/precommit`, which runs `script/lint`, which is the container prettier run. It would have failed there before ever reaching `script/fmt-check` anyway. The hook lost a redundant pass, not a capability. Host and container verdicts still cannot disagree, re-verified and now asserted by the test: prettier pinned at an exact `3.8.1`, installed under `--frozen-lockfile` in both places, and `.gitignore` still in the build context because prettier reads it as a default ignore file. Nothing in this change touches any of the three.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/quak#29