diff --git a/.agents/skills/hookdeck-cli-release/SKILL.md b/.agents/skills/hookdeck-cli-release/SKILL.md index 8a6fdf33..780373b4 100644 --- a/.agents/skills/hookdeck-cli-release/SKILL.md +++ b/.agents/skills/hookdeck-cli-release/SKILL.md @@ -30,6 +30,7 @@ Follow **in order**. Treat items with **gate** as blocking unless the maintainer - [ ] **Release notes:** Draft complete (see **Drafting release notes** and [references/release-notes-template.md](references/release-notes-template.md)); includes **Full Changelog** compare link; **contributor shout-outs only when warranted** (see that section). - [ ] **CI gate:** Latest commit on the **target branch** has **green** GitHub checks (mandatory for GA on `main`; required for betas on the branch being tagged). - [ ] **Approval:** Maintainer signed off on tag name, notes, and branch — no unilateral surprise tags. +- [ ] **Acceptance group is idle (gate):** no acceptance run in progress or queued — `gh run list --workflow=test-acceptance.yml --status in_progress` and `--status queued` both empty. The release gate shares the `acceptance-suite` concurrency group, which keeps only one *pending* run: if the release's acceptance run is left pending, a pull request pushed behind it cancels it, and the builds and npm publish that depend on it never run. Don't open or push PRs while a release is building. - [ ] **Publish:** Write notes to a **temporary file**, run **`gh release create`** (see **Publish with GitHub CLI (`gh`)**), then **`rm`** the temp file. Use `--prerelease` for betas. (Humans may still use the GitHub UI per README.) - [ ] **Post-publish (optional):** Confirm the **`release`** workflow in Actions completed successfully for the new tag. diff --git a/.agents/skills/hookdeck-cli-release/references/release-notes-voice.md b/.agents/skills/hookdeck-cli-release/references/release-notes-voice.md index f64abbbb..5872bad4 100644 --- a/.agents/skills/hookdeck-cli-release/references/release-notes-voice.md +++ b/.agents/skills/hookdeck-cli-release/references/release-notes-voice.md @@ -52,31 +52,47 @@ Always end with the compare link: `**Full Changelog**: https://github.com/hookdeck/hookdeck-cli/compare/...` -## Summary +## Factual, not editorial + +A release note says what was wrong, what the CLI does now, and what the reader has to do, if +anything. It does not interpret, characterise or narrate. + +| Editorial — cut it | Factual — write this | +|---|---| +| "The one to act on:" | "If you have used `--hookdeck-config` with v3.0.0 or v3.0.1, run `chmod 600` on that file." | +| "The other three share a shape — an argument you supplied did not survive the round trip." | *(nothing — the bullets already say what each fix is)* | +| "so a script filtering by a list of ids was told they did not exist" | "returned no results for `--id a,b`" | +| "an internal name, so an agent looking for the key it requested did not find it" | "returned `webhook_id` when `connection_id` was requested" | -Two to four short paragraphs. Lead with the capability or the theme, then the shape of the rest. -**Name the shared shape when fixes rhyme** — often one defect wearing different clothes, and saying -so is worth more than the list: +The test for each clause: is it a fact about the software, or an instruction? If it is a reading of +the facts — why it mattered, what it felt like, what it has in common with something else — cut it. +The issue link carries the reasoning for anyone who wants it. + +## Summary -> Alongside that is a long list of fixes sharing one shape — the CLI reported success while doing -> something other than what you asked. +One or two sentences: what the release contains, and any action a reader must take. A GA release +names its headline capability; a patch names what it fixes. No theme, no framing. ## Entries **Second person.** "your project", "your scripts" — never "the user". -**Lead with the fix, bolded, in the reader's terms.** Then the consequence. Then the link. +**Lead with the fix, bolded, in the reader's terms.** Then what was wrong, stated as behaviour. +Then the link. > - **`hookdeck ci --local` and `hookdeck login --local` no longer rewrite your global config.** -> `--local` added a second write rather than redirecting the first, so it silently switched the -> active project for every other `hookdeck` command on the machine — the opposite of what the flag -> is for. ([#332](https://github.com/hookdeck/hookdeck-cli/issues/332)) +> `--local` added a second write rather than redirecting the first, so it also switched the +> active project for every other `hookdeck` command on the machine. +> ([#332](https://github.com/hookdeck/hookdeck-cli/issues/332)) -That entry is 52 words and explains a subtle bug completely. Match that density. +That entry states the fix, the mechanism and the effect, with nothing else. (The v2.6.0 original +ended "— the opposite of what the flag is for"; that clause is commentary and is cut here.) -**Make the damage concrete, and bold it where it is the point.** +**State the wrong behaviour precisely** — the command, the input, the output. That is concrete +without being editorial: -> returned **unfiltered totals formatted as if filtered** +> `gateway event list`, `gateway request list` and `gateway transformation list` returned no results +> for `--id a,b`. **Show a command only when it earns the space** — a new flag people will copy, or output that makes a failure obvious. Not one per entry. @@ -141,6 +157,7 @@ The reader stops looking for a workaround. ## What this voice avoids - **Verbosity.** See the table. This is the failure mode. +- **Editorial framing.** Interpretation, characterisation, narrative. See *Factual, not editorial*. - **Marketing register.** No "we're excited", "powerful", "seamless". - **Hedging.** "may", "should" — say what it does. - **Labels that categorise the reader** ("who it reaches", "for advanced users"). diff --git a/.agents/skills/hookdeck-cli-review/SKILL.md b/.agents/skills/hookdeck-cli-review/SKILL.md index dff1adb3..bdefba28 100644 --- a/.agents/skills/hookdeck-cli-review/SKILL.md +++ b/.agents/skills/hookdeck-cli-review/SKILL.md @@ -66,6 +66,41 @@ than the colour: gh run view --job= --log | grep -E -- '--- (SKIP|PASS): ' ``` +### A cancelled acceptance run is not a run either + +Every acceptance run shares one concurrency group, `acceptance-suite`, so runs +against the same test projects cannot collide. The group holds **one running +run and one pending run**. When a third joins, the older *pending* one is +cancelled; `cancel-in-progress: false` protects only the one that is running. + +So when several pull requests are opened or pushed close together, every one +but the newest can end with all five acceptance jobs `CANCELLED`. That happened +to four v3.0.2 PRs in one afternoon. It is not a failure, and because +acceptance is not a required check, the merge button stays green. + +**Read `CANCELLED` as "never ran", and re-run it — one at a time.** Re-running +two together puts both in the single pending slot and cancels one again. Queue +the next only once the previous has started running: + +``` +gh run rerun +gh run view --json status -q .status # wait for in_progress, then the next +``` + +The release gate lives in the same group. A release whose acceptance run is +*pending* is cancelled by a pull request that joins behind it, and the builds +and npm publish that depend on it never run. Before a release, check that no +acceptance run is in progress or queued: + +``` +gh run list --workflow=test-acceptance.yml --status in_progress +gh run list --workflow=test-acceptance.yml --status queued +``` + +And if a workflow change is in the diff: opening that pull request triggers +acceptance too (`.github/workflows/**` is in the path allowlist), so it can +cancel someone else's pending run. + ### The one case where it genuinely does not run `pull_request` does not fire when a pull request's head branch is updated by diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index a38b0714..6cc9815c 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -16,14 +16,28 @@ on: # The failure is nasty because it does not look like rate limiting. It surfaces # as ordinary assertion failures in whichever slice lost the race -- a config # that came back empty, a filter that returned nothing -- sending whoever reads -# it hunting for a regression that does not exist. It has happened twice: once -# as four jobs timing out with zero assertion failures, and once as a single -# named test failing while a release ran concurrently. +# it hunting for a regression that does not exist. The clear case on record is +# four jobs timing out with zero assertion failures. # -# cancel-in-progress is deliberately false. Cancelling would be faster, but the -# release workflow calls this as its publish gate, and killing that run to make -# room for a pull request is the wrong trade. Queueing costs wall-clock and -# nothing else. +# (A red TestOutpostTenantPortalAndCustomDomain during a concurrent release was +# once blamed on this too. It was not: the custom-domain API can return a 500 +# after the domain is already created, and the retry then gets a 409 for the +# test's own hostname. Not every red run during an overlap is the overlap.) +# +# cancel-in-progress is deliberately false, so a RUNNING run is never killed to +# make room for another -- the release gate included. +# +# But this is not a queue. A concurrency group holds at most one running run and +# ONE pending run. When another run joins, the older pending run is CANCELLED. +# So several pull requests opened or pushed in a burst leave all but the newest +# with acceptance "CANCELLED": not a failure, but not coverage either, and since +# acceptance is not a required check nothing blocks the merge. Re-run them one +# at a time -- re-running two at once cancels one of them again. +# +# The same applies to the release gate when it is pending rather than running: +# a pull-request run joining the group cancels it, and the builds and npm +# publish that `need` it never run. Before `gh release create`, make sure no +# acceptance run is in progress or queued. concurrency: group: acceptance-suite cancel-in-progress: false