Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/skills/hookdeck-cli-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,47 @@ Always end with the compare link:

`**Full Changelog**: https://github.com/hookdeck/hookdeck-cli/compare/<prev>...<new>`

## 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.
Expand Down Expand Up @@ -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").
Expand Down
35 changes: 35 additions & 0 deletions .agents/skills/hookdeck-cli-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,41 @@ than the colour:
gh run view --job=<id> --log | grep -E -- '--- (SKIP|PASS): <TestName>'
```

### 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 <run-id>
gh run view <run-id> --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
Expand Down
28 changes: 21 additions & 7 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading