fix: Gate the release on the checks that gate a pull request #85

Merged
ahmad merged 2 commits from feature/84-release-checks into main 2026-09-10 07:13:58 +00:00
Owner

Issue

Closes #84

Problem

A release published and deployed without running any of the checks that gate a
pull request. ahmad/larda shipped v0.19.0 and v0.20.0 over a main whose
pnpm typecheck was failing, with every job green.

Solution

A checks job running the same commands the repository's PR workflow runs, which
the rest of the flow needs.

It gates the whole flow rather than sitting between version and publish.
That is the design decision worth reviewing: version tags and publishes the
Release
, so a check failing after it would leave a tag and release notes for a
version that never shipped — and consume that version number for the next one.
Gating first means a red tree produces nothing at all.

notify needs checks too. Without that a blocked release either reports a
generic failure or, since notify is if: always(), reports on three jobs that
never ran. Its details line names the case: "the checks on main did not pass
(failure) — nothing was tagged, published or deployed"
, because that outcome
wants different people looking than a deploy falling over.

Review notes

The checks steps in the template are the common pnpm shape and are meant to be
replaced with whatever the consumer's PR workflow runs — the README says so, and
so does the comment above the job. Keeping the two lists identical is the
point
; a check that gates a pull request and not a release is a check with a
hole in it.

The status expression was evaluated against all nine combinations that can reach
notify — table in the comment below — including the two new ones, checks
failed and checks cancelled.

check-actions.py now validates 13 run: steps in this template, up from 7, so
the new job's shell is covered by the existing gate.

The cost, measured

Every merge to main now runs the check suite, including merges that warrant no
release. Measured from the median of each repository's recent check-job runs:

seconds
suhba/khitta 9
Bayan/kidpoints 17
Bayan/stem 18
ahmad/launchpad 20
suhba/suhba-web, suhba/suhba-docs 24
Bayan/bayan-web 33
ahmad/portfolio 41
ahmad/imamah 51
ahmad/expiro 82
ahmad/namey 94
ahmad/larda 96

Median 29 seconds, worst case 96. Nothing here is slow enough to be a reason not
to, which is the answer criterion 4 asked for.

Follow-ups

All thirteen consumers have one, each carrying its own measured cost, filed
state/blocked on this: ahmad/trip#148, ahmad/portfolio#94,
ahmad/expiro#90, ahmad/larda#79, ahmad/namey#87, ahmad/imamah#165,
ahmad/launchpad#91, Bayan/stem#22, Bayan/bayan-web#37,
Bayan/kidpoints#216, suhba/suhba-web#47, suhba/suhba-docs#20,
suhba/khitta#60.

Risks and trade-offs

The template only; no action changes, so no consumer's behaviour moves until its
own follow-up lands.

All five validate steps pass locally by exit code.

### Issue Closes #84 ### Problem A release published and deployed without running any of the checks that gate a pull request. `ahmad/larda` shipped v0.19.0 and v0.20.0 over a `main` whose `pnpm typecheck` was failing, with every job green. ### Solution A `checks` job running the same commands the repository's PR workflow runs, which the rest of the flow needs. **It gates the whole flow rather than sitting between `version` and `publish`.** That is the design decision worth reviewing: `version` *tags and publishes the Release*, so a check failing after it would leave a tag and release notes for a version that never shipped — and consume that version number for the next one. Gating first means a red tree produces nothing at all. `notify` needs `checks` too. Without that a blocked release either reports a generic failure or, since `notify` is `if: always()`, reports on three jobs that never ran. Its `details` line names the case: *"the checks on main did not pass (failure) — nothing was tagged, published or deployed"*, because that outcome wants different people looking than a deploy falling over. ### Review notes The `checks` steps in the template are the common pnpm shape and are meant to be replaced with whatever the consumer's PR workflow runs — the README says so, and so does the comment above the job. **Keeping the two lists identical is the point**; a check that gates a pull request and not a release is a check with a hole in it. The status expression was evaluated against all nine combinations that can reach `notify` — table in the comment below — including the two new ones, `checks` failed and `checks` cancelled. `check-actions.py` now validates 13 `run:` steps in this template, up from 7, so the new job's shell is covered by the existing gate. ### The cost, measured Every merge to `main` now runs the check suite, including merges that warrant no release. Measured from the median of each repository's recent check-job runs: | | seconds | | --- | --- | | `suhba/khitta` | 9 | | `Bayan/kidpoints` | 17 | | `Bayan/stem` | 18 | | `ahmad/launchpad` | 20 | | `suhba/suhba-web`, `suhba/suhba-docs` | 24 | | `Bayan/bayan-web` | 33 | | `ahmad/portfolio` | 41 | | `ahmad/imamah` | 51 | | `ahmad/expiro` | 82 | | `ahmad/namey` | 94 | | `ahmad/larda` | 96 | Median 29 seconds, worst case 96. Nothing here is slow enough to be a reason not to, which is the answer criterion 4 asked for. ### Follow-ups All thirteen consumers have one, each carrying its own measured cost, filed `state/blocked` on this: `ahmad/trip#148`, `ahmad/portfolio#94`, `ahmad/expiro#90`, `ahmad/larda#79`, `ahmad/namey#87`, `ahmad/imamah#165`, `ahmad/launchpad#91`, `Bayan/stem#22`, `Bayan/bayan-web#37`, `Bayan/kidpoints#216`, `suhba/suhba-web#47`, `suhba/suhba-docs#20`, `suhba/khitta#60`. ### Risks and trade-offs The template only; no action changes, so no consumer's behaviour moves until its own follow-up lands. All five `validate` steps pass locally by exit code.
fix: Gate the release on the checks that gate a pull request
All checks were successful
PR / validate (pull_request) Successful in 19s
2525c813c3
A release published and deployed without running format, lint,
type-check or tests. ahmad/larda shipped v0.19.0 and v0.20.0 over a main
whose typecheck was failing, and every job was green.

Adds a checks job the rest of the flow needs. It gates the whole flow
rather than sitting between version and publish, because version tags and
publishes the Release — a check failing after it would leave a tag and
notes for a version that never shipped and burn the number.

notify now needs checks too and names that case in its details line: a
release blocked by a red tree is the one outcome where nothing downstream
even attempted.
ahmad changed title from WIP: fix: Gate the release on the checks that gate a pull request to fix: Gate the release on the checks that gate a pull request 2026-09-10 05:48:31 +00:00
Author
Owner

Self-verified on 2525c81; PR / validate green.

  • A main whose checks fail publishes and deploys nothing. Everything needs checks: version gains needs: [checks], and publish and deploy already chain from version. A failed gate means no tag either, which is stronger than the criterion asked and is the reason for gating first rather than between version and publish — version publishes the Release, so a later failure would leave notes for a version that never shipped and burn the number.
  • A main whose checks pass releases as before. The only change downstream of checks is version's needs.
  • A blocked release says the checks failed rather than reporting generically. notify needs checks, and its details line reads "the checks on main did not pass (failure) — nothing was tagged, published or deployed".
  • The added time is stated. Table in the pull request body: median 29s across thirteen repositories, suhba/khitta 9s at the fastest and ahmad/larda 96s at the slowest, taken from the median of each repository's recent check-job runs rather than estimated.
  • Every consumer has a follow-up. Thirteen, each state/blocked on this and each carrying its own measured cost.

The status expression evaluated against all nine reachable combinations, including the two new ones:

checks version publish deploy released →
success success success success true success
success success success skipped true success
success success skipped skipped false skipped
failure skipped skipped skipped — failure
cancelled skipped skipped skipped — cancelled
success success failure skipped true failure
success success success failure true failure
success failure skipped skipped — failure
success success success cancelled true cancelled

Gates by exit code: test-check-actions.py 0, check-actions.py 0, test-behaviour-harness.py 0, behaviour-tests.py 0, README lint 0. Worth noting check-actions.py now validates 13 run: steps in this template where it validated 7 — the new job's shell is covered by the gate that already existed.

One thing I want a second opinion on: the checks steps I wrote are the common pnpm shape, and every consumer will replace them. An alternative would be leaving them as <CHECKS> placeholders like <OWNER>/<APP>. I chose runnable-but-wrong over obviously-blank because it makes the shape clear — install, then the four gates — and because the comment and the README both say to replace them. If you would rather it be a placeholder, that is a one-line change and I have no strong view.

Self-verified on `2525c81`; `PR / validate` green. - *A `main` whose checks fail publishes and deploys nothing.* Everything needs `checks`: `version` gains `needs: [checks]`, and `publish` and `deploy` already chain from `version`. A failed gate means no tag either, which is stronger than the criterion asked and is the reason for gating first rather than between `version` and `publish` — `version` publishes the Release, so a later failure would leave notes for a version that never shipped and burn the number. - *A `main` whose checks pass releases as before.* The only change downstream of `checks` is `version`'s `needs`. - *A blocked release says the checks failed rather than reporting generically.* `notify` needs `checks`, and its details line reads "the checks on main did not pass (failure) — nothing was tagged, published or deployed". - *The added time is stated.* Table in the pull request body: median **29s** across thirteen repositories, `suhba/khitta` 9s at the fastest and `ahmad/larda` 96s at the slowest, taken from the median of each repository's recent check-job runs rather than estimated. - *Every consumer has a follow-up.* Thirteen, each `state/blocked` on this and each carrying its own measured cost. **The status expression evaluated against all nine reachable combinations**, including the two new ones: | checks | version | publish | deploy | released | → | | --- | --- | --- | --- | --- | --- | | success | success | success | success | true | `success` | | success | success | success | skipped | true | `success` | | success | success | skipped | skipped | false | `skipped` | | **failure** | skipped | skipped | skipped | — | **`failure`** | | **cancelled** | skipped | skipped | skipped | — | **`cancelled`** | | success | success | failure | skipped | true | `failure` | | success | success | success | failure | true | `failure` | | success | failure | skipped | skipped | — | `failure` | | success | success | success | cancelled | true | `cancelled` | Gates by exit code: `test-check-actions.py` 0, `check-actions.py` 0, `test-behaviour-harness.py` 0, `behaviour-tests.py` 0, README lint 0. Worth noting `check-actions.py` now validates **13** `run:` steps in this template where it validated 7 — the new job's shell is covered by the gate that already existed. One thing I want a second opinion on: the `checks` steps I wrote are the common pnpm shape, and every consumer will replace them. An alternative would be leaving them as `<CHECKS>` placeholders like `<OWNER>/<APP>`. I chose runnable-but-wrong over obviously-blank because it makes the *shape* clear — install, then the four gates — and because the comment and the README both say to replace them. If you would rather it be a placeholder, that is a one-line change and I have no strong view.
ahmad_bot requested changes 2026-09-10 06:21:46 +00:00
Dismissed
ahmad_bot left a comment

Requesting changes on one finding, with a second worth fixing in the same edit. The design is right and the status expression is sound — both were checked rather than taken from the description.

What I verified holds.

The status expression: I enumerated it under expression semantics (a && b yields a when falsy else b; a || b yields a when truthy else b) over all 512 combinations of the four job results and released. Every one returns one of the four status words, and no combination with a non-success checks can return success — the gate cannot be reported green. The nine reachable rows in your comment all match; I evaluated a tenth, checks success with version cancelled, which returns cancelled correctly.

Gating the whole flow rather than sitting between version and publish is the right call for the reason you give — version tags and publishes the Release, so a later failure would burn the version number — and notify needing checks is what makes the blocked case reportable at all.

The finding. PNPM_VERSION is undefined in this template, so the new job's corepack line silently activates the newest pnpm instead of the pinned one. Measured in the job's own image: it exits 0 and installs 12.4.0 where ahmad/larda's PR workflow pins 11.17.0. Detail inline.

The second point, inline as well: the instruction says to replace the steps, and for larda the gate lives partly in the job's services: block, a step-level DATABASE_URL and an explicit anti-skip assertion — without them pnpm test reports green with the store suites skipped, which is the state larda#49 closed.

One pre-existing wart in the line you are editing, not a reason to hold the merge and not in scope for #84 — worth an issue rather than a fix here. When version fails, released is empty, so details falls to its last branch and the notification reads ❌ Release — no release warranted by the commits. The status is right; the sentence beside it describes a different outcome. Your new first branch is what made me look, and it is the same shape: the checks case got named precisely because a generic line wants the wrong people looking.

On your open question — runnable-but-wrong over <CHECKS> placeholders: I would keep what you chose. The shape carries information a placeholder does not, and the two <PLACEHOLDER> conventions already in the file are for values, not blocks. It does argue for the PNPM_VERSION fix rather than against it: if the block is meant to be read as a working example, the example should work.

Requesting changes on one finding, with a second worth fixing in the same edit. The design is right and the status expression is sound — both were checked rather than taken from the description. **What I verified holds.** The status expression: I enumerated it under expression semantics (`a && b` yields `a` when falsy else `b`; `a || b` yields `a` when truthy else `b`) over all 512 combinations of the four job results and `released`. Every one returns one of the four status words, and **no combination with a non-success `checks` can return `success`** — the gate cannot be reported green. The nine reachable rows in your comment all match; I evaluated a tenth, `checks` success with `version` cancelled, which returns `cancelled` correctly. Gating the whole flow rather than sitting between `version` and `publish` is the right call for the reason you give — `version` tags and publishes the Release, so a later failure would burn the version number — and `notify` needing `checks` is what makes the blocked case reportable at all. **The finding.** `PNPM_VERSION` is undefined in this template, so the new job's corepack line silently activates the newest pnpm instead of the pinned one. Measured in the job's own image: it exits 0 and installs 12.4.0 where `ahmad/larda`'s PR workflow pins 11.17.0. Detail inline. **The second point**, inline as well: the instruction says to replace the *steps*, and for larda the gate lives partly in the job's `services:` block, a step-level `DATABASE_URL` and an explicit anti-skip assertion — without them `pnpm test` reports green with the store suites skipped, which is the state `larda#49` closed. **One pre-existing wart in the line you are editing**, not a reason to hold the merge and not in scope for #84 — worth an issue rather than a fix here. When `version` fails, `released` is empty, so `details` falls to its last branch and the notification reads **❌ Release — no release warranted by the commits**. The status is right; the sentence beside it describes a different outcome. Your new first branch is what made me look, and it is the same shape: the `checks` case got named precisely because a generic line wants the wrong people looking. On your open question — runnable-but-wrong over `<CHECKS>` placeholders: I would keep what you chose. The shape carries information a placeholder does not, and the two `<PLACEHOLDER>` conventions already in the file are for values, not blocks. It does argue for the `PNPM_VERSION` fix rather than against it: if the block is meant to be read as a working example, the example should work.
@ -57,0 +72,4 @@
# `main` is now reported when it happens, rather than discovered by whoever
# opens the next pull request.
#
# Replace the steps below with exactly what your PR workflow runs. Keeping
Member

Copying the step list alone loses the gate for at least one consumer — and it is ahmad/larda, the repository this issue was opened over.

Its validate job carries three things that are not run: lines:

  • a services: block running postgres:17-alpine with a health check;
  • a step-level env: DATABASE_URL on the test step;
  • a step after the tests that asserts the store suites actually ran.

Its own comment says why: "The store suites are skipped without DATABASE_URL, so without a database here they skipped on every pull request and a green validate said nothing about the SQL (#49)." And the assertion step is there because "a green validate with the store suites skipped ... looks identical to a real pass".

So a checks job assembled from the step list — install, format, lint, typecheck, pnpm test — passes green on larda while the integration suites skip. That is the state larda#49 closed, reintroduced in the job whose whole purpose is to stop a green pipeline from meaning nothing.

Fix: say in this comment, and in the README paragraph, that what is copied is the PR workflow's job, not its step list — services:, job- and step-level env:, and any assertion steps come with it. Copying the shape one line lower than the instruction says is exactly the mistake this text is trying to prevent, and thirteen follow-up issues are already pointed at it.

**Copying the step list alone loses the gate for at least one consumer — and it is `ahmad/larda`, the repository this issue was opened over.** Its `validate` job carries three things that are not `run:` lines: - a `services:` block running `postgres:17-alpine` with a health check; - a step-level `env: DATABASE_URL` on the test step; - a step after the tests that asserts the store suites actually ran. Its own comment says why: *"The store suites are skipped without DATABASE_URL, so without a database here they skipped on every pull request and a green `validate` said nothing about the SQL (#49)."* And the assertion step is there because *"a green `validate` with the store suites skipped ... looks identical to a real pass"*. So a `checks` job assembled from the step list — install, format, lint, typecheck, `pnpm test` — passes green on larda while the integration suites skip. That is the state `larda#49` closed, reintroduced in the job whose whole purpose is to stop a green pipeline from meaning nothing. **Fix:** say in this comment, and in the README paragraph, that what is copied is the PR workflow's **job**, not its step list — `services:`, job- and step-level `env:`, and any assertion steps come with it. Copying the shape one line lower than the instruction says is exactly the mistake this text is trying to prevent, and thirteen follow-up issues are already pointed at it.
@ -57,0 +81,4 @@
image: node:22-bookworm
steps:
- uses: actions/checkout@v4
- run: corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
Member

PNPM_VERSION is not defined anywhere in this template, so this line does not pin pnpm — it floats.

grep -n PNPM_VERSION templates/release.yml returns exactly one hit: this line. The workflow-level env: block holds REGISTRY and IMAGE_NAME and nothing else, and there is no job- or step-level env: on checks. The step list came from a consumer's PR workflow, but the env: block that gave the variable a value did not come with it — ahmad/larda's pr.yml sets PNPM_VERSION: 11.17.0 at workflow level and uses this exact line.

It does not fail, which is the problem. Measured in a throwaway node:22-bookworm, which is the image this job runs in:

$ corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
Preparing pnpm@* for immediate activation...
Downloading the pnpm 12.4.0 binary for linux-x64...
EXIT=0
$ pnpm --version
12.4.0

The empty expansion leaves pnpm@, corepack reads that as pnpm@* and installs the newest release. So the job that gates the release runs pnpm 12.4.0 where the job that gates the pull request runs 11.17.0 — a major apart — and exits 0 either way. I checked whether it breaks the install outright and it does not: a lockfileVersion: '9.0' lockfile written by 11.17.0 installs clean under 12.4.0 with --frozen-lockfile (exit 0), so nothing downstream reports it. It also floats: the next pnpm release changes this gate for every consumer with no commit anywhere.

That is the same hole this pull request is closing, one level down — a check that gates a release under a different toolchain than the one that gated the pull request.

Fix: give the template the variable, beside the two that are already there, and list it with the other placeholders in the header:

env:
  REGISTRY: git.ahmadelmasri.com
  IMAGE_NAME: <OWNER>/<APP>
  PNPM_VERSION: <PNPM-VERSION>
**`PNPM_VERSION` is not defined anywhere in this template, so this line does not pin pnpm — it floats.** `grep -n PNPM_VERSION templates/release.yml` returns exactly one hit: this line. The workflow-level `env:` block holds `REGISTRY` and `IMAGE_NAME` and nothing else, and there is no job- or step-level `env:` on `checks`. The step list came from a consumer's PR workflow, but the `env:` block that gave the variable a value did not come with it — `ahmad/larda`'s `pr.yml` sets `PNPM_VERSION: 11.17.0` at workflow level and uses this exact line. **It does not fail, which is the problem.** Measured in a throwaway `node:22-bookworm`, which is the image this job runs in: ``` $ corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate Preparing pnpm@* for immediate activation... Downloading the pnpm 12.4.0 binary for linux-x64... EXIT=0 $ pnpm --version 12.4.0 ``` The empty expansion leaves `pnpm@`, corepack reads that as `pnpm@*` and installs the newest release. So the job that gates the release runs **pnpm 12.4.0** where the job that gates the pull request runs **11.17.0** — a major apart — and exits 0 either way. I checked whether it breaks the install outright and it does not: a `lockfileVersion: '9.0'` lockfile written by 11.17.0 installs clean under 12.4.0 with `--frozen-lockfile` (exit 0), so nothing downstream reports it. It also floats: the next pnpm release changes this gate for every consumer with no commit anywhere. That is the same hole this pull request is closing, one level down — a check that gates a release under a different toolchain than the one that gated the pull request. **Fix:** give the template the variable, beside the two that are already there, and list it with the other placeholders in the header: ```yaml env: REGISTRY: git.ahmadelmasri.com IMAGE_NAME: <OWNER>/<APP> PNPM_VERSION: <PNPM-VERSION> ```
ahmad changed title from fix: Gate the release on the checks that gate a pull request to WIP: fix: Gate the release on the checks that gate a pull request 2026-09-10 06:30:02 +00:00
fix: Pin pnpm in the checks job and copy the whole job
All checks were successful
PR / validate (pull_request) Successful in 19s
f8b315288c
PNPM_VERSION had no definition in the template, so corepack read
pnpm@ as pnpm@* and activated the newest release — the release gate
ran a different toolchain than the pull-request gate, and exited 0
either way. It is now a placeholder beside the other two.

The instruction also said to copy the PR workflow's steps. A validate
job's services, env and assertion steps are often what makes its
checks mean anything, so it says the job.

Refs #84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TpKJno2d3VeZeEyV3sJbeq
Author
Owner

Both findings were right and both are fixed in f8b3152.

PNPM_VERSION floating. Confirmed before fixing: grep -n PNPM_VERSION templates/release.yml returned one hit, the corepack line itself, and the
workflow-level env: block held only REGISTRY and IMAGE_NAME. The variable
is now a placeholder beside those two and is listed in the header block with the
others, so it is filled in the same pass as the image name rather than
discovered later. Your measurement is the part that made this worth stopping
for — it exits 0, installs a major version ahead, and the lockfile installs
clean under it, so nothing downstream would ever have reported it.

Copying the steps rather than the job. Also fixed, in both places you named.
The template comment and the README paragraph now say to copy the PR workflow's
validate job — services:, job- and step-level env: and assertion steps come
with it — and both cite ahmad/larda#49 as the concrete case, because "copy the
job" without the reason is the kind of instruction that gets read as pedantry and
shortened back to the step list. The README no longer says "the same commands"
in its opening line either; that phrasing was the same mistake one paragraph up.

On the pre-existing details wart — version failing falls through to
"no release warranted by the commits", which describes a different outcome. I
agree it is real and I agree it is not #84's. Filed as amtronics/platform-actions#86
rather than folded in here.

On the open question, thank you — I have kept the runnable steps.

check-actions.py exits 0, the template still parses as YAML, and the README
lints clean.

Both findings were right and both are fixed in `f8b3152`. **`PNPM_VERSION` floating.** Confirmed before fixing: `grep -n PNPM_VERSION templates/release.yml` returned one hit, the corepack line itself, and the workflow-level `env:` block held only `REGISTRY` and `IMAGE_NAME`. The variable is now a placeholder beside those two and is listed in the header block with the others, so it is filled in the same pass as the image name rather than discovered later. Your measurement is the part that made this worth stopping for — it exits 0, installs a major version ahead, and the lockfile installs clean under it, so nothing downstream would ever have reported it. **Copying the steps rather than the job.** Also fixed, in both places you named. The template comment and the README paragraph now say to copy the PR workflow's validate *job* — `services:`, job- and step-level `env:` and assertion steps come with it — and both cite `ahmad/larda#49` as the concrete case, because "copy the job" without the reason is the kind of instruction that gets read as pedantry and shortened back to the step list. The README no longer says "the same commands" in its opening line either; that phrasing was the same mistake one paragraph up. **On the pre-existing `details` wart** — `version` failing falls through to "no release warranted by the commits", which describes a different outcome. I agree it is real and I agree it is not #84's. Filed as `amtronics/platform-actions#86` rather than folded in here. **On the open question**, thank you — I have kept the runnable steps. `check-actions.py` exits 0, the template still parses as YAML, and the README lints clean.
ahmad changed title from WIP: fix: Gate the release on the checks that gate a pull request to fix: Gate the release on the checks that gate a pull request 2026-09-10 06:35:32 +00:00
ahmad_bot approved these changes 2026-09-10 06:49:34 +00:00
ahmad_bot left a comment

Both findings are addressed. Re-reviewed the delta only — f8b3152 against 2525c81 — and the rest of the template is untouched, including the status expression I enumerated last time.

The pin. PNPM_VERSION: <PNPM-VERSION> is in the workflow env: block and listed with the other placeholders in the header. I checked the case this introduces, which is the one worth checking: a consumer who copies the template and forgets to replace it. It fails loudly rather than trading one silent state for another — measured in node:22-bookworm:

$ PNPM_VERSION='<PNPM-VERSION>' corepack prepare pnpm@${PNPM_VERSION} --activate
Usage Error: Tag not found (<PNPM-VERSION>)
EXIT=1

Exit 1 on the second step of the gate, before anything is installed. That is the right shape: the unpinned case was dangerous precisely because it exited 0, and an unfilled placeholder now cannot.

Copy the job, not the steps. Both the job comment and the README section now say the whole job — services:, job- and step-level env:, assertion steps — and name ahmad/larda#49 as the concrete case. That is what makes the instruction actionable for the thirteen follow-ups; a consumer reading it will look for its services: block instead of pasting run: lines.

PR / validate is green on this head.

The pre-existing details wart I mentioned stands unchanged and is still out of scope for #84 — worth its own issue: when version fails, released is empty, so a red release reports "no release warranted by the commits".

Both findings are addressed. Re-reviewed the delta only — `f8b3152` against `2525c81` — and the rest of the template is untouched, including the status expression I enumerated last time. **The pin.** `PNPM_VERSION: <PNPM-VERSION>` is in the workflow `env:` block and listed with the other placeholders in the header. I checked the case this introduces, which is the one worth checking: a consumer who copies the template and forgets to replace it. It fails loudly rather than trading one silent state for another — measured in `node:22-bookworm`: ``` $ PNPM_VERSION='<PNPM-VERSION>' corepack prepare pnpm@${PNPM_VERSION} --activate Usage Error: Tag not found (<PNPM-VERSION>) EXIT=1 ``` Exit 1 on the second step of the gate, before anything is installed. That is the right shape: the unpinned case was dangerous precisely because it exited 0, and an unfilled placeholder now cannot. **Copy the job, not the steps.** Both the job comment and the README section now say the whole job — `services:`, job- and step-level `env:`, assertion steps — and name `ahmad/larda#49` as the concrete case. That is what makes the instruction actionable for the thirteen follow-ups; a consumer reading it will look for its `services:` block instead of pasting `run:` lines. `PR / validate` is green on this head. The pre-existing `details` wart I mentioned stands unchanged and is still out of scope for #84 — worth its own issue: when `version` fails, `released` is empty, so a red release reports "no release warranted by the commits".
ahmad merged commit 9d6228c6cb into main 2026-09-10 07:13:58 +00:00
ahmad deleted branch feature/84-release-checks 2026-09-10 07:13:58 +00:00
Sign in to join this conversation.
No description provided.