Maintainer runbook for shipping a full Paperclip release across npm, GitHub, and the website-facing changelog surface.
This document is intentionally practical:
- TL;DR command sequences are at the top.
- Detailed checklists come next.
- Motivation, failure handling, and rollback playbooks follow after that.
## Release Surfaces
Every Paperclip release has four separate surfaces:
1.**Verification** — the exact git SHA must pass typecheck, tests, and build.
2.**npm** — `paperclipai` and the public workspace packages are published.
3.**GitHub** — the stable release gets a git tag and a GitHub Release.
4.**Website / announcements** — the stable changelog is published externally and announced.
Treat those as related but separate. npm can succeed while the GitHub Release is still pending. GitHub can be correct while the website changelog is stale. A maintainer release is done only when all four surfaces are handled.
## TL;DR
### Canary release
Use this when you want an installable prerelease without changing `latest`.
claude --print --output-format stream-json --verbose --dangerously-skip-permissions --model claude-opus-4-6 "Use the release-changelog skill to draft or update releases/v${VERSION}.md for Paperclip. Read doc/RELEASING.md and skills/release-changelog/SKILL.md, then generate the stable changelog for v${VERSION} from commits since the last stable tag. Do not create a canary changelog."
In authenticated/private mode, this smoke path also injects a smoke-only `BETTER_AUTH_SECRET` by default and prints the bootstrap CEO invite after the server becomes healthy.
If you want to exercise onboarding from a fresh local checkout rather than npm, use:
```bash
./scripts/clean-onboard-git.sh
```
That is not a required release step every time, but it is a useful higher-confidence check when onboarding is the main risk area or when you need to verify what the current codebase does before publishing.
There is also a manual workflow at [`.github/workflows/release.yml`](../.github/workflows/release.yml). It is designed for npm trusted publishing via GitHub OIDC instead of long-lived npm tokens.
Use it from the Actions tab:
1. Choose `Release`
2. Choose `channel`: `canary` or `stable`
3. Choose `bump`: `patch`, `minor`, or `major`
4. Choose whether this is a `dry_run`
5. Run it from `master`
The workflow:
- reruns `typecheck`, `test:run`, and `build`
- gates publish behind the `npm-release` environment
- can publish canaries without touching `latest`
- can publish stable, push the release commit and tag, and create the GitHub Release
## Release Checklist
### Before any publish
- [ ] The working tree is clean, including untracked files
- [ ] The target branch and SHA are the ones you actually want to release
The release workflow at [`.github/workflows/release.yml`](../.github/workflows/release.yml) installs with `pnpm install --frozen-lockfile`. That is intentional. Releases must use the exact dependency graph already committed on `master`; if manifests changed and the CI-owned lockfile refresh has not landed yet, the release should fail until that prerequisite is merged.
claude -p "Use the release-changelog skill to draft or update releases/v${VERSION}.md for Paperclip. Read doc/RELEASING.md and skills/release-changelog/SKILL.md, then generate the stable changelog for v${VERSION} from commits since the last stable tag. Do not create a canary changelog."
- ensure public docs and install guidance point to the stable version
## GitHub Actions and npm Trusted Publishing
If you want GitHub to own the actual npm publish, use [`.github/workflows/release.yml`](../.github/workflows/release.yml) together with npm trusted publishing.
Recommended setup:
1. Configure the GitHub Actions workflow as a trusted publisher for **every public package** on npm
2. Use the `npm-release` GitHub environment with required reviewers
3. Run stable publishes from `master` only
4. Keep the workflow manual via `workflow_dispatch`
Why this is the right shape:
- no long-lived npm token needs to live in GitHub secrets
- reviewers can approve the publish step at the environment gate
- the workflow reruns verification on the release SHA before publish
- stable and canary use the same mechanics
## Failure Playbooks
### If the canary fails before publish
Nothing shipped. Fix the code and rerun the canary workflow.
### If the canary publishes but the smoke test fails
Do **not** publish stable.
Instead:
1. Fix the issue
2. Publish another canary
3. Re-run smoke testing
The canary version number will increase, but the stable target version can remain the same.
### If the stable npm publish succeeds but push fails
This is a partial release. npm is already live.
Do this immediately:
1. Fix the git issue
2. Push the release commit and tag from the same checkout
3. Create the GitHub Release
Do **not** publish the same version again.
### If the stable release is bad after `latest` moves
Use the rollback script first:
```bash
./scripts/rollback-latest.sh <last-good-version>
```
Then:
1. open an incident note or maintainer comment
2. fix forward on a new patch release
3. update the changelog / release notes if the user-facing guidance changed
### If the GitHub Release is wrong
Edit it by re-running:
```bash
./scripts/create-github-release.sh X.Y.Z
```
This updates the release notes if the GitHub Release already exists.
### If the website changelog is wrong
Fix the website independently. Do not republish npm just to repair the website surface.
## Rollback Strategy
The default rollback strategy is **dist-tag rollback, then fix forward**.
Why:
- npm versions are immutable
- users need `npx paperclipai onboard` to recover quickly
- moving `latest` back is faster and safer than trying to delete history
Rollback procedure:
1. identify the last known good stable version
2. run `./scripts/rollback-latest.sh <version>`
3. verify `npm view paperclipai@latest version`
4. fix forward with a new stable release
## Scripts Reference
- [`scripts/release.sh`](../scripts/release.sh) — stable and canary npm publish flow