| name | hermes-hookdeck-release |
| description | Guides maintainers through releasing the hermes-hookdeck plugin to PyPI. Publishing a GitHub release is what triggers the publish, and the tag is the version — nothing in the repo is bumped. Validates the proposed version against SemVer from the actual change set, including the rule that a change to the bundled skill or plugin.yaml is a shipped change. Use when cutting a release, publishing to PyPI, drafting release notes, choosing vMAJOR.MINOR.PATCH, `gh release create`, or following the release checklist. |
hermes-hookdeck — release workflow
Canonical documentation
Follow README.md § Releasing for the human steps
(GitHub UI). This skill adds how the automation works, the gates that
must pass first, and a research loop for drafting notes.
The tag is the version
Nothing in the repo declares a version. setuptools-scm derives it from the git
tag at build time and bakes it into hookdeck/_version.py inside the wheel, so
hookdeck.__version__ and the PyPI version are the same string by
construction and cannot drift.
The practical consequence: you choose the number when you create the
release, not in a commit beforehand. There is nothing to bump, and no
pre-release step that can be forgotten.
PyPI is append-only. A version number that has been published can never be
reused or replaced, even after a yank. That is why the number is worth getting
right before you publish — it is the one part of this that cannot be undone.
Between releases a source checkout reports a dev version derived from the last
tag (0.1.2.dev4+g1a2b3c4). That is expected, not a bug: it says "four commits
past v0.1.1".
Agent checklist (end-to-end)
Follow in order. Items marked gate are blocking unless the maintainer
explicitly overrides.
What triggers a release
Publishing a GitHub release — .github/workflows/release.yml
runs on release: [published]. There is no separate approval step: publishing
the release is the decision to ship.
Pushing a bare tag does nothing. The tag is created by the release.
What the workflow does
- build — checks out the release's tag with full history (setuptools-scm
needs the tag present to resolve the version), runs
ruff and the full test
suite, builds the wheel and sdist, and runs twine check.
- publish — uploads to PyPI via Trusted Publishing (OIDC, no stored
token), in the
pypi environment.
- attach-artifacts — attaches the wheel and sdist to the GitHub release
you created, so it carries the artifacts that actually went to PyPI.
A failure in build means nothing was published; fix main and publish a
new release. A failure in publish can be re-run from the Actions tab
(gh run rerun <id> --failed) — do not create a second release for the same
version.
Because the version comes from the tag, a mistyped tag produces a real release
at the wrong number rather than an error. Check the tag before publishing; that
is the check the workflow can no longer do for you.
SemVer: validate the proposed version
Check any proposed tag against what actually changed since PREV_TAG.
The contract this package offers is its configuration, its environment
variables, its CLI, its agent tools, and the Hermes surfaces it registers.
Change since PREV_TAG | Bump | Examples |
|---|
| Breaking — an existing install stops working, or behaves differently without the operator changing anything | MAJOR | Renamed or removed config key or HOOKDECK_EG_* variable; a default that changes delivery behaviour; a removed hermes hookdeck subcommand; a renamed or removed hookdeck_* agent tool; a ledger schema change that an older row cannot satisfy |
| New capability, backward compatible | MINOR | New config option with a safe default; new subcommand or agent tool; a new route feature; support for a Hermes version that was not supported before |
| Fixes and corrections, no new capability, nothing breaks | PATCH | Bug fixes; corrections to the bundled skill or plugin.yaml; dependency bumps with no behaviour change; packaging metadata |
A change under hookdeck/ ships even when it is only prose. The bundled
skill (hookdeck/skills/), plugin.yaml and the dashboard bundle are inside
the wheel, so correcting them is a patch release, not a docs-only change.
A change outside hookdeck/ usually does not ship. docs/, README.md,
AGENTS.md, tests/, skills/ at the repo root and .github/ are not
packaged. A release containing only those has nothing for a user to install —
say so rather than cutting one.
Verify which of the two you are in rather than assuming:
git diff --stat PREV_TAG..origin/main -- hookdeck/
Empty output means nothing shipped.
Agent behaviour: state the minimum bump the change set requires, then
compare it to what was proposed. If they conflict, do not treat the proposal
as authoritative — explain the mismatch and recommend the correct version. If
it is genuinely ambiguous whether something breaks an existing install, ask.
Publish with GitHub CLI (gh)
Create the release with gh rather than pushing a tag — a bare tag does not
trigger anything.
-
Write the notes to a temp file and register cleanup, so a failure does
not leave it behind:
NOTES_FILE="$(mktemp "${TMPDIR:-/tmp}/hermes-hookdeck-release-notes.XXXXXX.md")"
trap 'rm -f "$NOTES_FILE"' EXIT
-
Write the final markdown body to "$NOTES_FILE".
-
Create the release, targeting main:
gh release create "vM.m.p" \
--repo hookdeck/hermes-hookdeck \
--target main \
--title "vM.m.p" \
--notes-file "$NOTES_FILE"
Add --prerelease for rc, a or b versions, so the repository's
"latest release" does not point at a release candidate.
-
Watch it: gh run watch — or gh run list --workflow=release.yml --limit 1.
Requirements: gh authenticated. Nothing needs to be bumped first — the
tag you pass here becomes the version. Do not put secrets in the notes file.
Verify afterwards
pip download --no-deps -d /tmp/verify hermes-hookdeck==M.m.p
For a release that changed anything under hookdeck/, confirm the change is
really in the artifact rather than only in the repo — install it into a clean
venv and check the file, not the working tree.
Drafting release notes
Use references/release-notes-template.md
as the skeleton. Include only headings with real content — omit a section
rather than writing "None".
Write for someone who runs a gateway, not for someone who reads this repo. The
useful question is "what changes for me, and do I have to do anything?" — not
"which files moved".
- Lead with anything that requires action. Config changes, renamed
variables, anything that alters delivery behaviour. If nothing does, say the
upgrade is a drop-in.
- A fix is only worth a bullet if the reader could have hit it. Say what
went wrong from the outside ("the bundled skill never loaded"), not what the
code did.
- Always end with the compare link:
https://github.com/hookdeck/hermes-hookdeck/compare/<prev_tag>...<new_tag>
Contributors: do not add a generic thanks block every release. Include one
only for a first-time contributor or an exceptionally large contribution.
Research loop
-
Tags: git tag --sort=-v:refname | head -5. Confirm PREV_TAG.
-
Commits: git log PREV_TAG..origin/main — full messages. This repo
writes the reasoning into commit bodies; that is the changelog source.
-
Shipped or not: git diff --stat PREV_TAG..origin/main -- hookdeck/.
-
Group by user impact, not by file.
-
SemVer check against the table above.
-
CI:
gh api "repos/hookdeck/hermes-hookdeck/commits/$(git rev-parse origin/main)/check-runs" \
--jq '.check_runs[] | "\(.name): \(.status)/\(.conclusion // "-")"'
Every run must be completed/success. Do not release otherwise.
Ask for check-runs, not /status. The latter reports the legacy commit
Status API, which this repo does not use — with no statuses recorded it
answers pending forever, so it reads as a red gate on a green main.
Safety and governance
- PyPI is append-only. A published version cannot be reused, replaced, or
truly deleted. Yanking hides it from resolvers; it does not free the number.
- Do not release a red
main. The workflow runs the suite itself and will
fail the build, but finding out during a release is the wrong time.
- Do not under-bump. Resolve a SemVer disagreement with the maintainer
before publishing, not after.
- The tag is unchecked. Nothing compares it to the change set, so a typo
(
v0.2.0 for a patch) ships at that number and burns it permanently.
- Green tests are not proof of integration. The suite runs against a stub
this repo also owns. Both defects found after 200 green tests came from a
real
hermes gateway run. For a release touching the delivery path, say
which level you actually exercised — see AGENTS.md.
Related files