| name | cli-release |
| description | Runbook for releasing the `executor` CLI package (stable and beta). Covers scope of what ships with the CLI, user-facing changelog conventions, Changesets + Version Packages PR flow, beta train entry/exit, and owner preferences. Use when the user asks to cut a release, prepare release notes, enter/exit a beta train, or write changesets for the CLI. |
Executor CLI release runbook
Authoritative doc
RELEASING.md at repo root is the source of truth. This skill encodes the owner's preferences on top of it.
What the executor CLI actually ships
The CLI binary bundles:
apps/cli/** — CLI source + daemon
apps/local/** — the web UI (embedded as a virtual module via apps/cli/src/build.ts:178) + drizzle migrations (build.ts:205)
packages/** — core, kernel, hosts/mcp, runtime-quickjs, and every plugin under packages/plugins/**
Does not ship in the CLI:
apps/cloud/** (Cloudflare Workers deployment)
apps/marketing/**, apps/desktop/**
examples/**, tests/**
Implication for changelogs: when asked "what changed since the last release", scope is git log v<last>..HEAD -- apps/cli apps/local packages, not just apps/cli. Skipping apps/local and packages misses the bulk of product changes (Connections UI, OAuth plugins, SDK scope, OTEL, etc.).
Versioning preferences
- Prior convention in this repo uses
patch bumps for feature-heavy releases (see .changeset/executor-1.4.6-beta.md for precedent). Don't push back on patch unless there are genuine SemVer-breaking API changes to a library consumer surface.
- Breaking CLI UX changes (removed flags, changed argv shape) have historically still been
patch bumps. Follow the owner's call — ask, don't assume minor.
- Normal release/patch PRs must add a
.changeset/*.md file with frontmatter like "executor": patch. Do not directly bump apps/cli/package.json or bun.lock in a feature/fix PR.
- Only the Changesets-generated
Version Packages PR should move apps/cli/package.json. If a normal PR directly changes that version, merging it to main can make .github/workflows/release.yml tag the commit and dispatch publish-executor-package.yml, causing an immediate CLI publish.
@executor-js/* library packages have their own publish path.
Release notes: single source of truth, curated, not auto-generated
The owner doesn't want GitHub's auto-generated "PR title by @user" list. Release notes live at apps/cli/release-notes/ and apps/cli/src/release.ts prefers them over --generate-notes.
apps/cli/release-notes/next.md is the canonical user-facing changelog. Per-package workspace CHANGELOG.md files are one-line stubs required by changesets/action@v1 (the GitHub Action wrapping the CLI in release.yml) — it reads each bumped package's CHANGELOG.md to build the Version Packages PR description and crashes with ENOENT if any is missing. The stubs satisfy that read; don't put release content in them.
How it's wired
apps/cli/src/release.ts reads apps/cli/release-notes/next.md and uses
its contents as the GitHub Release body. If the file is missing or empty,
falls back to gh release create --generate-notes. There's no
per-version archive in the repo — historical release bodies live on
GitHub Releases (durable, indexed, linkable).
Writing conventions
Structure release-notes files as:
## Highlights
### <user-facing story> # e.g. "Per-user OAuth for OpenAPI and MCP sources"
bullets of concrete user value
## New presets # optional
## Performance # optional
## Fixes
## Breaking changes
### <specific surface>
before / after code blocks for migrations
Lead with user-visible stories, not commit subjects. Group related commits into one story (e.g. 6 commits about Connections → one "Per-user OAuth" section). Include before/after CLI snippets for any breaking change. Keep bullets single-line.
Attribution
External contributor bullets end with Thanks @<user> (#PR):
- OAuth2 client-credentials flow end-to-end. Thanks @octocat (#456)
Do not Thanks maintainers, bots, or the repo owner — the lint script rejects @claude, @anthropic, @github-actions, @dependabot, @renovate, @rhyssullivan, @rhys-sullivan. Run bun run lint:release-notes before pushing notes.
When drafting from git log
- Look at
git diff v<last>..HEAD -- README.md first — it's the best single view of user-facing changes.
- Read commit messages in bulk (
git log --oneline v<last>..HEAD -- apps/cli apps/local packages), then bucket by theme before writing prose.
- Don't list every commit. Merge PRs and refactor-chain commits into one line.
Pairing with changesets
- A
.changeset/*.md describes the version bump (semver level + a short summary for the Version Packages PR description). It is not the user-facing changelog.
- If your PR adds a
.changeset/*.md for the executor package, also edit apps/cli/release-notes/next.md for the user-facing story. They have different audiences.
- The
.changeset/*.md body can be a one-liner pointing at the release-notes section it expands; users read the GitHub release body, not the changeset.
- Frontmatter is
"executor": patch (or minor/major if owner says so).
Starting a new release cycle
There's no post-release rename step. When you start work on the next
release, replace the existing next.md content with new entries — the
previous cycle's content is already preserved on the matching vX.Y.Z
GitHub Release page, so overwriting locally is safe. If next.md content
looks stale (i.e. mentions features already shipped), that's the signal
to clear it.
Beta release flow
git checkout -b rs/beta-v<next>-start
bun run release:beta:start # creates .changeset/pre.json
# write .changeset/executor-<next>-beta.md (patch frontmatter by default)
# write apps/cli/release-notes/next.md (curated notes)
git add ... && git commit # ONLY when owner says commit
git push -u origin rs/beta-v<next>-start
# Open PR -> merge -> release.yml opens "Version Packages (beta)" PR -> merge to publish
- Published under npm dist-tag
beta.
- Users install:
npm i -g executor@beta.
- Exit the train with
bun run release:beta:stop when going back to stable.
Stable release flow
Identical to beta except skip release:beta:start/stop. Changesets produce a normal Version Packages PR; merging publishes under latest.
Owner preferences (hard rules)
- Never commit until the owner explicitly says so. Set everything up in the working tree, run
git status, and stop.
- No AI / Claude / Anthropic / Co-Authored-By trailers in commits, commit messages, PRs, or any generated file. This is in
CLAUDE.md — do not violate.
- Branch naming:
rs/<short-topic> for Rhys's branches. Beta-start branch: rs/beta-v<version>-start.
- Remote:
origin = https://github.com/RhysSullivan/executor.git. If another remote appears (e.g. a fork remote), ask whether to remove it.
- Dirty working tree: if there are uncommitted changes when starting a release, ask whether to include them, stash them, or commit separately first. Don't sweep them into the release commit silently.
- Don't estimate time — code is cheap to write. Focus on what to do, not how long it takes.
- Fact-check scope claims before publishing. If release notes say "does not affect X", verify by reading the diff.
Common commands
bun run changeset # interactive; or write .changeset/*.md directly
bun run lint:release-notes # validate apps/cli/release-notes/next.md
bun run release:beta:start # enter prerelease
bun run release:beta:stop # exit prerelease
bun run release:publish:dry-run # build full CLI payload without publishing
bun run release:publish:packages:dry-run # pack @executor-js/* without publishing
bun run release:check # invoked by publish workflow
What the workflow does after merge to main
.github/workflows/release.yml opens/updates a Version Packages PR.
- Merging that PR:
- Publishes every
@executor-js/* library that's not yet on npm (via scripts/publish-packages.ts).
- If
apps/cli/package.json bumped, tags the commit and dispatches publish-executor-package.yml, which runs release:check, does a full dry-run build, publishes the CLI to npm, and creates/updates the GitHub Release with binary assets.
Fallback behavior
If something is unclear (bump level, whether to include in-flight work, whether to push), ask the owner. A release is a high-blast-radius action; one clarifying question is cheaper than a rogue publish.