# N8n Nodes Hookdeck Release

> Guides maintainers through releases of @hookdeck/n8n-nodes-hookdeck (stable from main, beta from main or a feature branch) and user-centric GitHub release notes. Validates the proposed version against SemVer as an n8n community node reads it, where a renamed node type or credential breaks saved workflows. Use when cutting a release, publishing a tag, drafting release notes, choosing MAJOR.MINOR.PATCH, npm publish, provenance, pre-releases, n8n verification, or `gh release create`.

- Skill: `hookdeck/n8n-nodes-hookdeck-release` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add hookdeck/n8n-nodes-hookdeck-release`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hookdeck/n8n-nodes-hookdeck-release/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: hookdeck (https://skillmd.com/u/hookdeck)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hookdeck/n8n-nodes-hookdeck-release

---


# @hookdeck/n8n-nodes-hookdeck — release workflow

## Canonical documentation

Follow **[CONTRIBUTING.md](../../CONTRIBUTING.md) § Releasing** for the human steps. This
skill adds **how automation works**, **what counts as breaking for an n8n node**,
and a **research loop** for drafting notes.

**Agents:** publish with the **GitHub CLI** — see **Publish with `gh`** below.

## Agent checklist (end-to-end)

Follow **in order**. Items marked **gate** are blocking unless the maintainer
explicitly overrides.

- [ ] **Release shape:** stable from `main`, beta from `main`, or beta from a
      feature branch. Confirm with the maintainer.
- [ ] **`PREV_TAG` / `NEW_TAG`:** agreed. For a beta series the baseline is
      usually the last **stable** tag, not the last beta.
- [ ] **Change set:** `git log PREV_TAG..HEAD` read in full, grouped by user
      impact (see **Research loop**).
- [ ] **gate — SemVer:** the proposed tag matches the **minimum** bump the delta
      requires (see **What breaks an n8n node**). Stop and realign if under-bumped.
- [ ] **gate — node identity unchanged:** no node `name`, credential `name` or
      parameter `name` changed since the last stable release without a MAJOR bump
      and a migration note. These are recorded in saved workflows.
- [ ] **gate — verification scan:** `npm run scan` passes. A release that fails it
      would fail n8n's review.
- [ ] **gate — CI green:** the commit being released has green checks. For a
      stable release that means the tip of `main`.
- [ ] **gate — release PR merged:** `version` in `package.json` set to the new
      version **and** `## [Unreleased]` in the CHANGELOG promoted to that version
      with a date, in one normal PR **before** the release. The publish workflow
      refuses a tag that disagrees with `package.json`, and n8n's verification
      review compares `main` against npm.
- [ ] **Notes drafted:** see **Drafting release notes** and
      [references/release-notes-template.md](references/release-notes-template.md).
      Include the **Full Changelog** compare link.
- [ ] **gate — approval:** the maintainer has signed off on the version, the notes
      and the target branch. Never push a surprise tag.
- [ ] **Publish:** notes to a temp file → `gh release create` → remove the temp
      file. `--prerelease` for betas.
- [ ] **Post-publish:** the **Publish** workflow succeeded, and the version is on
      npm under the expected dist-tag.

## What triggers a release

**[.github/workflows/publish.yml](../../.github/workflows/publish.yml)** runs on
`release: published` — not on a tag push, and not on a branch push. Creating the
release in the GitHub UI or with `gh release create` creates the tag and starts
the workflow together.

The workflow checks out the release tag, **fails if the tag does not match
`version` in `package.json`**, then re-runs lint, scan, build, the load check and
the unit tests, then `npm publish --provenance`. A **pre-release** publishes
under the `beta` dist-tag so `npm install @hookdeck/n8n-nodes-hookdeck` keeps
resolving to the last stable version.

`package.json` is the version and the tag agrees with it. The bump lands on
`main` in the release PR, before the release exists — never as a follow-up
commit after publishing, which leaves a window where `main` and npm disagree.
That window is exactly what n8n flagged in the 0.2.0 review, when `main` still
read `0.1.0`.

If the guard fails, the release is already created and the tag already exists.
Bump `package.json` on `main`, then delete both and recreate the release:

```bash
gh release delete v0.3.0 --repo hookdeck/n8n-nodes-hookdeck --cleanup-tag --yes
```

Editing the failed release does not re-run the workflow — it fires on
`release: published`.

## Auth: provenance is not trusted publishing

Two things, easily conflated:

- **Provenance** — the signed attestation tying the package to this repo,
  workflow and commit. Needs `id-token: write`. **This is what n8n requires.**
- **Trusted publishing** — publishing with a short-lived OIDC token instead of a
  long-lived npm token. Not required by n8n, and how this package publishes.

The workflow contains no token handling. npm >= 11.5.1 finds the trusted
publisher itself and exchanges the Actions OIDC token during publish, so there
is nothing to inject.

**Never add an `NPM_TOKEN` secret.** It is not a fallback: any credential in
`.npmrc` takes precedence over OIDC, so a stale or empty secret becomes the
publishing identity, or fails the publish.

The trusted publisher is already configured (owner `hookdeck`, repo
`n8n-nodes-hookdeck`, workflow `publish.yml`, environment blank). Leave the
environment blank — `publish.yml` declares no `environment:`, and npm matches
the OIDC claim exactly.

Publishing by hand is blocked: `prepublishOnly` runs `n8n-node prerelease`,
which exits unless `RELEASE_MODE` is set. Do not set it to work around a failing
release — a hand publish carries no provenance, which is what n8n requires.

## What breaks an n8n node

SemVer here is about **the contract with a saved workflow**, not just the API
surface. n8n stores the node type, the credential name and every parameter name
inside the user's workflow JSON. Renaming any of them does not fail a build — it
silently detaches existing workflows.

| Change since `PREV_TAG` | Bump | Examples |
| --- | --- | --- |
| **Breaking** — an existing published workflow stops working or loses configuration | **MAJOR** | Renaming a node type (`hookdeckEventGateway`), renaming the credential type (`hookdeckEventGatewayApi`), renaming or removing a parameter, removing a resource or operation, changing the shape of the trigger's output item, changing a default in a way that alters delivery behaviour |
| **New capability**, backward compatible | **MINOR** | New resource or operation, new option, new source types, additive fields on the output item, a new delivery route that existing workflows are not moved onto |
| **Fixes, docs, internals** | **PATCH** | Bug fixes, wording, icons, tests, CI, dependency bumps, generated source-type refreshes |

**Before 1.0.0**, a MINOR bump is the strongest signal available for a breaking
change, so say so loudly in the notes rather than relying on the number.

**Ask, do not guess.** If it is unclear whether a change detaches an existing
workflow, ask the maintainer before tagging. The cost of over-bumping is a
version number; the cost of under-bumping is someone's production workflow.

### The node-identity check, concretely

```bash
git diff PREV_TAG..HEAD -- nodes credentials \
  | grep -E '^[-+]\s+(name:|displayName:)' | sort | uniq -c | sort -rn | head -20
```

Any `- name: '...'` paired with a `+ name: '...'` in a node description, a
credential class or a property is a candidate break. Read it, do not skim it.

## Stable release

1. Land everything, including the release PR that bumps `package.json` and
   promotes the CHANGELOG, through PRs.
2. Confirm `main` is green:

   ```bash
   SHA=$(git rev-parse origin/main)
   gh api graphql -f query='
     query($owner:String!,$repo:String!,$sha:GitObjectID!){
       repository(owner:$owner,name:$repo){
         object(oid:$sha){ ... on Commit { statusCheckRollup { state } } }
       }
     }' -F owner=hookdeck -F repo=n8n-nodes-hookdeck -F sha="$SHA" \
     --jq '.data.repository.object.statusCheckRollup.state'
   ```

   Do not release on `FAILURE`, or on `PENDING` for required checks. The states
   are uppercase.

   Use the rollup, not `gh api .../commits/${SHA}/status`. That endpoint reads
   legacy commit statuses, which GitHub Actions does not write, so it returns
   `pending` however green CI is.
3. Create the release targeting `main` (see **Publish with `gh`**).

## Pre-release (beta)

Tag as `v0.3.0-beta.1`, with `package.json` set to the same `0.3.0-beta.1` on
the branch being released. The **base version** still has to satisfy the table
above relative to the last stable release — a beta containing a breaking change
is `v1.0.0-beta.1`, not `v0.9.0-beta.1`.

- **From `main`:** `--target main --prerelease`. Still requires green CI.
- **From a feature branch:** `--target <branch> --prerelease`. Requires green CI
  on that branch, and the notes should say what to test.

Install with `npm install @hookdeck/n8n-nodes-hookdeck@beta`.

## Publish with `gh`

Create the **release**, not a bare tag: the release carries the notes and is what
the workflow listens for.

1. Write the notes to a temp file, with cleanup registered up front:

   ```bash
   NOTES_FILE="$(mktemp "${TMPDIR:-/tmp}/n8n-nodes-hookdeck-notes.XXXXXX.md")"
   trap 'rm -f "$NOTES_FILE"' EXIT
   ```

2. Write the final markdown body to `"$NOTES_FILE"`.

3. Create the release:

   ```bash
   gh release create "v0.2.0" \
     --repo hookdeck/n8n-nodes-hookdeck \
     --target main \
     --title "v0.2.0" \
     --notes-file "$NOTES_FILE"
   ```

   Add `--prerelease` for a beta, and `--target <branch>` for a branch beta.

4. Confirm the workflow ran and the package landed:

   ```bash
   gh run list --repo hookdeck/n8n-nodes-hookdeck --workflow Publish --limit 1
   npm view @hookdeck/n8n-nodes-hookdeck dist-tags
   ```

**Never put a secret in the notes.** The release body is public.

## Drafting release notes

Start from
[references/release-notes-template.md](references/release-notes-template.md).

Write for someone running a workflow, not someone reading the diff. "Events are
no longer lost when the CLI restarts" beats "changed teardown from delete to
disable".

**Include only headings with real content.** Do not write "Breaking changes:
none".

Always end with:

`**Full Changelog**: https://github.com/hookdeck/n8n-nodes-hookdeck/compare/<PREV_TAG>...<NEW_TAG>`

**Say what a user must do.** If a release changes delivery behaviour, needs a
workflow republished to take effect, or requires the Hookdeck CLI to be
restarted, that belongs at the top, not in a bullet halfway down.

**Contributors:** only call out a **new** contributor shipping their first work,
or an exceptionally large contribution. No generic thanks block.

## Research loop

1. **Tags:** `git describe --tags --abbrev=0` on the target branch, or ask.
2. **Commits:** `git log PREV_TAG..HEAD --oneline`, then read the full messages.
   This repo writes long commit messages that explain *why* — use them; they are
   usually closer to release-note prose than the diff is.
3. **Group by user impact**, merging related commits.
4. **SemVer check** against the table above, plus the node-identity check.
5. **PRs:** `gh pr list --state merged --search "merged:>=<date>"` for links.
6. **Sanity:** if a commit is unclear, read the README section it changed —
   user-facing behaviour is documented there.
7. **gate — CI** on the branch being released.

## Safety

- Do not release with a failing `npm run scan`. It is the same rule set n8n runs
  for verification, and a failure there is a rejected submission.
- Do not under-bump. A renamed node type in a PATCH release detaches workflows
  silently.
- Do not publish from a laptop. n8n requires provenance from GitHub Actions, and
  a local `npm publish` produces none.
- Do not tag a version that `package.json` on the target branch does not carry.
  The workflow stops it, and the fix costs a deleted release and tag.
- Respect branch protection; no surprise tags.

## Related files

| Topic | Location |
| --- | --- |
| Human steps | [CONTRIBUTING.md § Releasing](../../CONTRIBUTING.md) |
| Publish workflow | [.github/workflows/publish.yml](../../.github/workflows/publish.yml) |
| CI | [.github/workflows/ci.yml](../../.github/workflows/ci.yml) |
| Live API tests | [.github/workflows/integration.yml](../../.github/workflows/integration.yml) |
| Change history | [CHANGELOG.md](../../CHANGELOG.md) |
| Notes template | [references/release-notes-template.md](references/release-notes-template.md) |

