Files
PaperClipAI/doc/RELEASE-CHECKLIST.md
T
Devin Foley 664052f8ea feat(release): draft stable notes at beta publish, read them from master at promotion (#11567)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The release channel system promotes builds canary → nightly → beta →
stable, and stable releases publish a GitHub Release from
`releases/vYYYY.MDD.P.md`
> - The stable lane requires that notes file to exist inside the
promoted source commit, but the file is named for the promotion date,
which is unknown when the source commit is created
> - A promoted beta can therefore never pass the notes check: every
happy-path stable is forced through the candidate-branch fix path, with
a soak-gate justification, for a notes-only change
> - This pull request drafts the notes automatically when the beta is
published and lets the stable promotion read them from `master`
> - The benefit is a walkable stable happy path: the soak gate stays
exact, notes get a real review window during the soak, and the
justification path returns to its real purpose (cherry-picked fixes)

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The stable promotion path in the release channel system (`release.yml`,
`scripts/release.sh`).

**Current behavior**

`release.sh stable` requires `releases/vYYYY.MDD.P.md` in the
checked-out source tree, and `publish_stable` checks out the exact
promoted SHA. The soak gate requires a `beta/v*` tag to point at that
same SHA. No commit can satisfy both for a promoted beta, so a stable
promotion must cut a candidate branch with a notes-only commit and
bypass the soak gate with a written justification. Release notes are
also written at promotion time, under time pressure, with no review
window.

**Proposed behavior**

When a beta publishes, a `draft_stable_notes` job generates a grouped
notes skeleton at `releases/beta/v<beta-version>.md` and pushes it to a
machine-owned branch; a human opens the PR and edits it during the 3-day
soak. The stable preflight resolves notes before the `npm-stable`
approval gate: source-tree notes first (the candidate fix path,
unchanged), then the merged beta-keyed file on `master`; it fails early
with the missing path named when neither exists. After the stable ships,
a canonicalization job pushes a branch that moves the file to
`releases/vYYYY.MDD.P.md`.

Related (not duplicates): #11006 and #11008 introduced the nightly and
beta lanes this builds on; older changelog PRs (for example #10669)
authored notes manually at promotion time, which is the flow this
replaces.

**Reason and benefit**

The happy path becomes: promote the exact soaked SHA, no justification,
notes reviewed during the soak instead of written at the gate. The
`releases/vYYYY.MDD.P.md` invariant still holds durably via the
canonicalization PR.

## What Changed

- `scripts/release.sh`: new `--notes-file PATH` (stable only) overrides
where the pre-publish notes check looks, so notes can live outside the
source checkout without dirtying the worktree.
- `scripts/create-github-release.sh`: same `--notes-file` override for
the GitHub Release body.
- `scripts/draft-stable-notes.sh` (new): deterministic skeleton
generator — commit subjects from the newest stable tag (falling back to
the previous beta, then full history) to the beta's source commit,
grouped into Features / Fixes / Other.
- `.github/workflows/release.yml`:
- `draft_stable_notes` job after `publish_beta`: runs the generator and
force-pushes `release-notes/v<beta-version>`; the job summary links the
compare page. It recreates the beta tag locally if the tag push was
rejected (the known workflows-permission case), so drafting is not
blocked on manual tag recovery.
- `preflight_stable`: computes the target stable version (`release.sh
stable --print-version`) and resolves the notes source (`source_tree` →
`master_beta` → fail early / warn on dry run); new outputs.
- `publish_stable`: materializes `master`-side notes into `RUNNER_TEMP`
and passes `--notes-file` to both scripts; outputs the published stable
version.
- `canonicalize_stable_notes` job: pushes the `git mv` branch after a
stable that used `master`-side notes.
- `doc/RELEASING.md`, `doc/RELEASE-CHECKLIST.md`: document the
drafted-notes flow, the preflight resolution order, and the
canonicalization step; the LLM changelog flow now targets the draft
branch during the soak.
- `.agents/skills/release-changelog/SKILL.md`,
`.agents/skills/release-changelog-discord-message/SKILL.md`: the
notes-authoring skills now describe this flow — range ends at the beta
source commit (not `HEAD`), the file is beta-keyed on the
`release-notes/v<beta-version>` branch (seeded with
`scripts/draft-stable-notes.sh` for betas that predate the automation),
and the canonicalization link caveat is called out for announcements.

## Verification

- `node --test scripts/draft-stable-notes.test.mjs` — 6 tests, temp
git-repo fixtures: grouping, stable-tag range, previous-beta and
full-history fallbacks, default output path, malformed version, missing
tag.
- `node --test scripts/release-lib.test.mjs` — unchanged suite still
green.
- `bash -n` on both changed shell scripts; `release.yml` re-parsed as
YAML.
- `./scripts/release.sh stable --print-version` unchanged (prints the
next stable version); `--notes-file` on a non-stable channel fails with
a clear error.
- Not exercised end-to-end: the new workflow jobs need a real beta
publish to run. The first beta after merge is the live test; the draft
job is additive and cannot affect the publish result (it runs after
`publish_beta` completes).

## Risks

- Low risk to publishing itself: `--notes-file` defaults preserve
today's behavior everywhere; the draft and canonicalization jobs are
additive and run after the publishes succeed.
- The preflight now fails a real stable run when no notes are found.
That is the intended fail-early behavior (it previously failed later,
inside `publish_stable`, after the `npm-stable` approval).
- `draft_stable_notes` force-pushes only the machine-owned
`release-notes/v<beta-version>` branch; a beta re-cut regenerates it
cleanly.
- The stable version computed at preflight could differ from the
published one if a run crosses UTC midnight between the two jobs; the
materialized notes are passed by path, so the publish still succeeds,
and the canonicalization job uses the actually-published version.

## Model Used

Claude Fable 5 (Claude Code)



## Pre-submission checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
2026-08-17 20:47:23 -07:00

3.8 KiB

Release Checklist

The release captain's checklist for every lane. The mechanics live in RELEASING.md; the user-facing channel guide is CHANNELS.md.

Canary (automatic, every master push)

  • the push's Release run is green (verify + publish)
  • npm view paperclipai@canary version matches the expected canary
  • Docker :canary updated (the same push's Docker run)
  • a canary publish failure is a release-infra regression — fix it before trusting later promotions

Nightly (automatic, 09:00 UTC)

  • the scheduled run selected the newest green canary, or skipped with a job-summary reason (no new candidate / already shipped / red smoke)
  • the release smoke suite passed against the exact candidate canary before anything published
  • npm view paperclipai@nightly version shows the new -nightly.N
  • nightly/v* tag pushed; :nightly and :nightly-cloud images built
  • on a tag-push rejection (workflows-permission error), follow the recovery commands in the job summary

To force a nightly: dispatch release.yml with channel: nightly (optional exact canary in source_version; dry_run to preview).

Beta (manual promotion)

Happy path:

  • pick the nightly to promote (empty source_version selects the newest)
  • dispatch release.yml with channel: beta
  • approve the npm-beta environment gate
  • npm view paperclipai@beta version shows the new -beta.N
  • beta/v* tag pushed; :beta and :beta-cloud images built
  • post-publish smoke (smoke_beta) is green
  • draft_stable_notes pushed release-notes/v<beta-version>; open the notes PR from the job-summary link
  • during the soak: edit the notes PR into release voice and merge it (the stable promotion reads releases/beta/v<beta-version>.md from master)

Fix path (cherry-picked candidate):

  • cut candidate/beta-<target> from the chosen nightly's source commit
  • cherry-pick only the required fixes; push the branch
  • dispatch channel: beta with candidate_branch
  • confirm the job summary records the cherry-picked commits and that full verification ran on the candidate head
  • after shipping: reconcile the fixes to master, delete the branch

Stable (manual promotion)

  • pick the beta to promote; its source commit is source_ref
  • the beta has soaked ≥ 3 days with no open beta-blocker issues
  • the beta's notes PR (releases/beta/v<beta-version>.md) is merged on master — preflight fails, before the approval gate, without it
  • dispatch release.yml with channel: stable (a dry run first shows the resolved version and soak state without publishing)
  • approve the npm-stable environment gate
  • npm view paperclipai version (dist-tag latest) shows the stable
  • vYYYY.MDD.P tag pushed; GitHub Release created; :latest and the versioned Docker tags built
  • if the soak gate was bypassed, skip_soak_justification carries a real written reason (it lands in the job summary)
  • open and merge the canonicalization PR (release-notes/v<version>-canonicalize) so the notes land at releases/vYYYY.MDD.P.md

Fix path: candidate/release-<target> from the beta's source commit; the soak gate will demand a justification because the exact bits were not soaked — write one that stands on its own. A candidate branch carries its own releases/vYYYY.MDD.P.md (preflight prefers source-tree notes), so author the notes as a commit on the candidate.

After any incomplete run

The failure playbooks in RELEASING.md cover: red canary, skipped or failed nightly, a beta that looks bad during soak, partial stable releases, broken latest, and rejected tag pushes. Every publish job's summary names what completed and what remains.