> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Release

> Changesets per member, the version-packages pull request, tags and recovery.

> [!NOTE]
>
> Status: partly live, partly target. `@changesets/cli` is configured (`.changeset/config.json`) and
> `pnpm changeset`, `pnpm changeset:status` and `pnpm changeset:version` exist.
> `.github/workflows/release.yml` opens the version pull request and, on its merge, runs `changeset
>     publish` to npmjs with provenance (pnpm trusted publishing, no token). Not built: a
> packed-artifact smoke test and signed artifacts. Today every package is still `"private": true`,
> so nothing publishes yet. Policy: [0025](/adr/0025-packages-publish-to-private-registry.md)
> (supersedes 0013 decision 3), [0177](/adr/0177-package-distribution-npmjs-and-keygen.md) (npmjs,
> provenance), [0024](/adr/0024-mit-license-with-exclusions.md) (MIT),
> [0119](/adr/0119-merge-policy-and-release-authority.md) (the owner merges the version pull request).

## The rules

* **Every change to a member ships with a changeset.** `pnpm changeset` records it; `pnpm changeset:status` fails when a changed member has none (target: chained into `pnpm verify`).
* **`CHANGELOG.md` files are generated** by changesets and never hand-edited.
* **Versions are bumped by the release job on `main`**, not on a feature branch. `guard-bash` denies `changeset version` locally for that reason.
* **Merging the version pull request is the release decision.** Claude prepares it; the owner merges it ([0119](/adr/0119-merge-policy-and-release-authority.md) D7).
* **Published or tagged versions are permanent.** A version is never republished or silently replaced; roll forward with a new patch.
* **Only packages are versioned.** Apps and services deploy by commit SHA and get no version or tag (`privatePackages: { version: false, tag: false }`, [0025](/adr/0025-packages-publish-to-private-registry.md) decision 4).
* **A package publishes when it flips to `"private": false`.** It then builds to `dist/*.mjs` + `.d.mts` and publishes to public npmjs (`@stackra` scope) with provenance.

## A normal release

**1. Land changesets with the work**

In each pull request that changes a member: `pnpm changeset`, pick the members, pick the bump
(patch, minor, major) and write one user-facing line. Commit the generated file under
`.changeset/`.

**2. Let CI open the version pull request**

After a push to `main` the release job runs `pnpm changeset:version` (which also refreshes the
lockfile) and opens or updates one pull request titled `chore(release): version packages`.

**3. Review it**

Read the `CHANGELOG.md` diffs and the version bumps. Check the bump matches the change. The
release job pushes and opens it as `stackra-bot` (a GitHub App token from
`actions/create-github-app-token`, repo secrets `STACKRA_BOT_APP_ID` and
`STACKRA_BOT_PRIVATE_KEY`), so the Quality Gate runs on it without a close and reopen.

**4. Merge it**

The merge to `main` is the release. The job builds, publishes every non-private package to
npmjs, tags it and creates the release notes.

**5. Verify**

`git ls-remote --tags origin` shows the new tags on the merge commit, and each member's
`CHANGELOG.md` contains the entry.

## Before the first release (once, by a human)

* Branch protection on `main` requires the `Quality Gate` check ([CI](/runbooks/ci.md)).
* A tag ruleset on the version tag pattern blocks deletion and non-fast-forward updates for everyone. Version tags are immutable.
* The host setting that lets the workflow token create pull requests is on.
* The release job runs in a protected environment restricted to `main`, so a branch that edits the workflow cannot obtain a credential.

## Publishing to npmjs

[0177](/adr/0177-package-distribution-npmjs-and-keygen.md) (amendment 2026-10-21) records the decision. The release job holds no npm token: changesets runs pnpm 12's native publish, which exchanges the job's GitHub OIDC token for a short-lived publish credential and signs a provenance statement. Each package needs a `repository` field matching `figentra/stackra` (the validator checks it).

**One-time owner steps, per package** (the `@stackra` org is reserved, STA-154):

**1. Flip the package**

In the package, set `"private": false` (its `publishConfig` is already there; `pnpm validate` requires it).

**2. Register the trusted publisher**

Needs 2FA enabled on the owner's npm account for `npm trust` (and the web page). npmjs.com → the package → Settings →
Trusted publishing → GitHub Actions: organization or user `figentra`, repository `stackra`, workflow filename
`release.yml`, environment `release`. Or with the CLI (npm 11.5 or later; one configuration per package):
`npm trust github @stackra/<name> --repo figentra/stackra --file release.yml --env release --allow-publish`.

**3. Optionally close the token path**

Same page, Publishing access → require two-factor authentication and disallow tokens.

**First publish of a brand-new package.** A trusted publisher can only be registered for a package that already exists on
the registry (`npm trust` says so too), so the job cannot create one. Publish the first version once by hand as an owner,
from the package directory after `pnpm build`:
`pnpm publish --no-git-checks --provenance=false --access public`. Use pnpm, not plain `npm publish`: npm would publish literal
`workspace:` and `catalog:` versions, and it fails on provenance outside CI. Then register the trusted publisher above. The
first version is not provenance-attested; ship a `0.0.x` placeholder.

**Rehearse before any real release.** After registering `@stackra/brand`: run the `Release` workflow with `dry_run` ticked
(`pnpm -r publish --dry-run`, same job and environment). Then publish a throwaway `0.0.x` of `@stackra/brand` from CI through a
merged version pull request, and check `npm view @stackra/brand dist.attestations` shows a provenance entry. Only then release
anything real.

The procedure the job follows:

* **No token anywhere.** No `.npmrc` auth line, repository secret or Infisical entry for npm.
* Build and publish with `id-token: write` only on the release job, in the protected `release` environment restricted to `main`.
* Publish is idempotent: a version already on the registry is skipped, so re-running failed jobs is safe.
* Consumers need no account or token to read `@stackra/*`.
* A prerelease publishes under a `next` dist-tag and moves no floating tag.
* `npm.figentra.com` still serves `@figentra/cli` and vellum; it is not the `@stackra` registry any more.

## When something fails

Re-run only the failed jobs of the same run unless a row says otherwise.

| Failed step | State | Do |
| - | - | - |
| Version pull request not opened | Nothing released | Check the host setting for pull requests from workflows, then push any commit to `main` or re-run |
| `verify` on the version pull request | Nothing released | Close and reopen the pull request; if it is red, fix on `main` and let the job update it |
| Tagging | Merged, not tagged | Re-run the failed job; it tags over an existing release safely |
| Wrong bump merged | Tagged | Roll forward: add a changeset and release the correction |

## Revert

* **Before merging:** close the version pull request. The next push recreates it from the remaining changesets.
* **After merging:** roll forward. For a bad release that was published, deprecate it (`npm deprecate <pkg>@<bad> "<reason; use <good>>"`), move the `latest` dist-tag back to the last good version with `npm dist-tag add` if needed, and ship a fixed patch. Prefer deprecate over unpublish.
* **A bad release record on the host:** mark it a prerelease or delete the release page; leave the tag.
* **The workflow itself:** revert the workflow file on `main`; nothing else depends on it.

## Checks that back this page

| Rule | Check | State |
| - | - | - |
| Changeset per changed member | `pnpm changeset:status` | Script live; not in `verify` |
| No local `changeset version` | `guard-bash` | Live |
| Conventional commits and generated changelogs | commitlint, changesets | Live |
| Release workflow, tag ruleset | CI | Not built |

See also [Mobile release](/runbooks/mobile-release.md), [Deploys](/runbooks/deploys.md) and [Commits and releases](/guides/workflow.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.