Skip to content

Build1 publisher3 min readPublished

A trailing dollar sign disarms the fixture check in this merge-promotion hook

The four-stage path from a local green check to main is worth adopting, but the reference pre-push hook anchors its forbidden-paths regex so tightly that no real fixture file can match it, leaving the escape hatch unreachable.

The Engineer · Build desk

Illustration accompanying A trailing dollar sign disarms the fixture check in this merge-promotion hook

What happened

  • A dev.to tutorial argues that merge is a promotion rather than a consequence of a green check, and lays out four stages where each one writes evidence the next stage refuses to start without.
  • Stage one is a local pre-push receipt, promote-receipt/v1, recording the git and tree SHAs, a fixture lock digest, the changed paths, and two booleans for allowlist and fixture status.
  • The article discloses that it was prepared as part of MonkeyCode's product outreach, and calls its own scripts a proposed workflow for a throwaway branch.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint The allowlist only admits src/ and tests/test_ prefixes, so a README or workflow-file edit trips the local gate; adopters will either widen the pattern per repo or start passing --no-verify, and a hook people skip is not a gate.
  • decision Anyone copying the hook has to decide what a legitimate fixture update looks like in their repo, because the trailer the article documents as the approved override cannot currently let a push through.
  • contradiction The design leans on CI recomputing the freeze so the receipt is not merely a self-report, yet the workflow that would do the recomputing is promised and not shown, so only the stage running on the machine that wrote the diff can be audited.

Read the hook's two regexes together, because its behaviour comes out of their overlap. The allowlist is `^(src/|tests/test_)` [4]. The forbidden list is `^(tests/fixtures/|package-lock\.json|uv.lock|poetry.lock)$` [5]. The second pattern is anchored at both ends, so a changed path matches only if the whole string equals one of those four alternatives, and `tests/fixtures/quote.json` does not equal `tests/fixtures/`. The flag the tutorial is built around, `fixtures_updated`, stays false for every file inside the fixtures directory [9].

The push is still refused, by the other branch. That same path also fails the allowlist, so `allowlist_ok` is false and the hook exits with "path allowlist failed; split the unrelated files out" [7][8]. The three lockfiles that do match the forbidden pattern exactly also fail the allowlist. Every path that can set `fixtures_updated` true is therefore a path that fails `allowlist_ok`, and adding `Fixtures-Update: true` to the commit message cannot make any push succeed [10]. The documented route for a legitimate fixture update exists as code and not as a reachable state.

The collateral runs the other way too. A README edit, a `pyproject.toml` bump, or a change under `.github/workflows/` matches neither `src/` nor `tests/test_`, so the hook blocks a docs-only push [22]. Meanwhile a golden file or snapshot committed under `src/` passes both booleans, because the allowlist admits everything under `src/` and the forbidden list names only three lockfiles at the repository root [21]. That is the exact class of diff the piece says an agent will produce [17].

The freeze value has a smaller quirk. `fixture_lock` is built by piping `sha256sum` output through `sha256sum` [11], and `sha256sum` prints each file's path beside its digest, so renaming a fixture changes the lock while the bytes stay identical [12]. Defensible if you chose it on purpose, confusing to inherit by accident. The `commands` array is written with `exit: 0` as a literal [13], and pytest and ruff run earlier in the same script under `set -euo pipefail` [14], so the receipt only exists when both passed; the recorded exit codes assert that the script reached its last line rather than what the tests returned [15].

The staging argument is the part worth taking, and it is well made. CI answers whether this SHA failed the jobs you paid to run, while merge answers whether this SHA is allowed to become everyone else's default [16]. Those questions come apart when an agent wrote the diff, because the agent can rewrite the fixture that made the test pass and spread the change into lockfiles, snapshots, or golden files, and the boolean check goes green anyway [17]. Four stages that each leave evidence the next stage refuses to start without is the right shape for that problem [1][2].

What the published text does not contain is stages two through four. The article promises a GitHub Actions workflow and a decision table alongside the schema and the hook [18], and it ends at the hook. The recompute in CI is the only step that turns the receipt from a self-report into evidence, and here it is asserted. The piece also discloses that it was prepared as part of MonkeyCode's product outreach [19], and its author calls the scripts a proposed workflow for a throwaway branch rather than a claim about your production fleet [18]. That label is honest, and it is the one to keep on those two regexes before pasting them into a repo whose layout is not `src/` and `tests/test_`.

What to watch

  • Whether the promised GitHub Actions workflow recomputes fixture_lock from the checked-out tree or simply reads .ci/receipt.json, which decides whether stage two is a check or a formality.
  • Whether the forbidden-paths regex is corrected to a prefix match, and whether the allowlist grows beyond src/ and tests/test_ to cover docs and CI config.
  • Any published detail on the merge-queue stage, since separating one flake retry from a real failure is asserted in the outline but not specified in the text.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories