| # Releasing Paperclip |
|
|
| Maintainer runbook for shipping Paperclip across npm, GitHub, and the website-facing changelog surface. |
|
|
| The release model is now commit-driven: |
|
|
| 1. Every push to `master` publishes a canary automatically. |
| 2. Stable releases are manually promoted from a chosen tested commit or canary tag. |
| 3. Stable release notes live in `releases/vYYYY.MDD.P.md`. |
| 4. Only stable releases get GitHub Releases. |
|
|
| ## Versioning Model |
|
|
| Paperclip uses calendar versions that still fit semver syntax: |
|
|
| - stable: `YYYY.MDD.P` |
| - canary: `YYYY.MDD.P-canary.N` |
|
|
| Examples: |
|
|
| - first stable on March 18, 2026: `2026.318.0` |
| - second stable on March 18, 2026: `2026.318.1` |
| - fourth canary for the `2026.318.1` line: `2026.318.1-canary.3` |
|
|
| Important constraints: |
|
|
| - the middle numeric slot is `MDD`, where `M` is the UTC month and `DD` is the zero-padded UTC day |
| - use `2026.303.0` for March 3, not `2026.33.0` |
| - do not use leading zeroes such as `2026.0318.0` |
| - do not use four numeric segments such as `2026.3.18.1` |
| - the semver-safe canary form is `2026.318.0-canary.1` |
|
|
| ## Release Surfaces |
|
|
| Every stable release has four separate surfaces: |
|
|
| 1. **Verification** — the exact git SHA passes typecheck, tests, and build |
| 2. **npm** — `paperclipai` and public workspace packages are published |
| 3. **GitHub** — the stable release gets a git tag and GitHub Release |
| 4. **Website / announcements** — the stable changelog is published externally and announced |
|
|
| A stable release is done only when all four surfaces are handled. |
|
|
| Canaries only cover the first two surfaces plus an internal traceability tag. |
|
|
| ## Core Invariants |
|
|
| - canaries publish from `master` |
| - stables publish from an explicitly chosen source ref |
| - tags point at the original source commit, not a generated release commit |
| - stable notes are always `releases/vYYYY.MDD.P.md` |
| - canaries never create GitHub Releases |
| - canaries never require changelog generation |
|
|
| ## TL;DR |
|
|
| ### Canary |
|
|
| Every push to `master` runs the canary path inside [`.github/workflows/release.yml`](../.github/workflows/release.yml). |
|
|
| It: |
|
|
| - verifies the pushed commit |
| - computes the canary version for the current UTC date |
| - publishes under npm dist-tag `canary` |
| - creates a git tag `canary/vYYYY.MDD.P-canary.N` |
|
|
| Users install canaries with: |
|
|
| ```bash |
| npx paperclipai@canary onboard |
| # or |
| npx paperclipai@canary onboard --data-dir "$(mktemp -d /tmp/paperclip-canary.XXXXXX)" |
| ``` |
|
|
| ### Stable |
|
|
| Use [`.github/workflows/release.yml`](../.github/workflows/release.yml) from the Actions tab with the manual `workflow_dispatch` inputs. |
|
|
| [Run the action here](https://github.com/paperclipai/paperclip/actions/workflows/release.yml) |
|
|
| Inputs: |
|
|
| - `source_ref` |
| - commit SHA, branch, or tag |
| - `stable_date` |
| - optional UTC date override in `YYYY-MM-DD` |
| - enter a date like `2026-03-18`, not a version like `2026.318.0` |
| - `dry_run` |
| - preview only when true |
|
|
| Before running stable: |
|
|
| 1. pick the canary commit or tag you trust |
| 2. resolve the target stable version with `./scripts/release.sh stable --date "$(date +%F)" --print-version` |
| 3. create or update `releases/vYYYY.MDD.P.md` on that source ref |
| 4. run the stable workflow from that source ref |
|
|
| Example: |
|
|
| - `source_ref`: `master` |
| - `stable_date`: `2026-03-18` |
| - resulting stable version: `2026.318.0` |
|
|
| The workflow: |
|
|
| - re-verifies the exact source ref |
| - computes the next stable patch slot for the chosen UTC date |
| - publishes `YYYY.MDD.P` under npm dist-tag `latest` |
| - creates git tag `vYYYY.MDD.P` |
| - creates or updates the GitHub Release from `releases/vYYYY.MDD.P.md` |
|
|
| ## Local Commands |
|
|
| ### Preview a canary locally |
|
|
| ```bash |
| ./scripts/release.sh canary --dry-run |
| ``` |
|
|
| ### Preview a stable locally |
|
|
| ```bash |
| ./scripts/release.sh stable --dry-run |
| ``` |
|
|
| ### Publish a stable locally |
|
|
| This is mainly for emergency/manual use. The normal path is the GitHub workflow. |
|
|
| ```bash |
| ./scripts/release.sh stable |
| git push public-gh refs/tags/vYYYY.MDD.P |
| PUBLISH_REMOTE=public-gh ./scripts/create-github-release.sh YYYY.MDD.P |
| ``` |
|
|
| ## Stable Changelog Workflow |
|
|
| Stable changelog files live at: |
|
|
| - `releases/vYYYY.MDD.P.md` |
|
|
| Canaries do not get changelog files. |
|
|
| Recommended local generation flow: |
|
|
| ```bash |
| VERSION="$(./scripts/release.sh stable --date 2026-03-18 --print-version)" |
| 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 .agents/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." |
| ``` |
|
|
| The repo intentionally does not run this through GitHub Actions because: |
|
|
| - canaries are too frequent |
| - stable notes are the only public narrative surface that needs LLM help |
| - maintainer LLM tokens should not live in Actions |
|
|
| ## Smoke Testing |
|
|
| For a canary: |
|
|
| ```bash |
| PAPERCLIPAI_VERSION=canary ./scripts/docker-onboard-smoke.sh |
| ``` |
|
|
| For the current stable: |
|
|
| ```bash |
| PAPERCLIPAI_VERSION=latest ./scripts/docker-onboard-smoke.sh |
| ``` |
|
|
| Useful isolated variants: |
|
|
| ```bash |
| HOST_PORT=3232 DATA_DIR=./data/release-smoke-canary PAPERCLIPAI_VERSION=canary ./scripts/docker-onboard-smoke.sh |
| HOST_PORT=3233 DATA_DIR=./data/release-smoke-stable PAPERCLIPAI_VERSION=latest ./scripts/docker-onboard-smoke.sh |
| ``` |
|
|
| Automated browser smoke is also available: |
|
|
| ```bash |
| gh workflow run release-smoke.yml -f paperclip_version=canary |
| gh workflow run release-smoke.yml -f paperclip_version=latest |
| ``` |
|
|
| Minimum checks: |
|
|
| - `npx paperclipai@canary onboard` installs |
| - onboarding completes without crashes |
| - authenticated login works with the smoke credentials |
| - the browser lands in onboarding on a fresh instance |
| - company creation succeeds |
| - the first CEO agent is created |
| - the first CEO heartbeat run is triggered |
|
|
| ## Rollback |
|
|
| Rollback does not unpublish versions. |
|
|
| It only moves the `latest` dist-tag back to a previous stable: |
|
|
| ```bash |
| ./scripts/rollback-latest.sh 2026.318.0 --dry-run |
| ./scripts/rollback-latest.sh 2026.318.0 |
| ``` |
|
|
| Then fix forward with a new stable patch slot or release date. |
|
|
| ## Failure Playbooks |
|
|
| ### If the canary publishes but smoke testing fails |
|
|
| Do not run stable. |
|
|
| Instead: |
|
|
| 1. fix the issue on `master` |
| 2. merge the fix |
| 3. wait for the next automatic canary |
| 4. rerun smoke testing |
|
|
| ### If stable npm publish succeeds but tag push or GitHub release creation fails |
|
|
| This is a partial release. npm is already live. |
|
|
| Do this immediately: |
|
|
| 1. push the missing tag |
| 2. rerun `PUBLISH_REMOTE=public-gh ./scripts/create-github-release.sh YYYY.MDD.P` |
| 3. verify the GitHub Release notes point at `releases/vYYYY.MDD.P.md` |
|
|
| Do not republish the same version. |
|
|
| ### If `latest` is broken after stable publish |
|
|
| Roll back the dist-tag: |
|
|
| ```bash |
| ./scripts/rollback-latest.sh YYYY.MDD.P |
| ``` |
|
|
| Then fix forward with a new stable release. |
|
|
| ## Related Files |
|
|
| - [`scripts/release.sh`](../scripts/release.sh) |
| - [`scripts/release-package-map.mjs`](../scripts/release-package-map.mjs) |
| - [`scripts/create-github-release.sh`](../scripts/create-github-release.sh) |
| - [`scripts/rollback-latest.sh`](../scripts/rollback-latest.sh) |
| - [`doc/PUBLISHING.md`](PUBLISHING.md) |
| - [`doc/RELEASE-AUTOMATION-SETUP.md`](RELEASE-AUTOMATION-SETUP.md) |
|
|