Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .release-it.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
Comment thread
ThomasK33 marked this conversation as resolved.
"git": {
"commit": false,
"tag": false,
"push": false,
"requireBranch": false,
"requireCleanWorkingDir": false,
"requireCommits": false,
"requireUpstream": false
},
"npm": {
"publish": false
},
"github": {
"release": false
},
"gitlab": {
"release": false
}
}
13 changes: 13 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ A persisted JSON artifact containing exactly the **Snapshot Result** returned to
The operation that derives a **Snapshot Result** from a **Semantic Snapshot** and records the matching **Snapshot Artifact**.
_Avoid_: Renderer capture

**Release Prep Workflow**:
The maintainer-facing process for choosing the next release version and preparing release changes for review before they land on the default branch.

**Release Finalization Step**:
The post-merge process that creates and publishes the release tag from the default branch after release prep changes have landed.

**Publish Pipeline**:
The tag-triggered automation that validates, packages, and publishes a release after the **Release Finalization Step**.

## Relationships

- A **Session** has exactly one **Session Status** at a time.
Expand All @@ -73,6 +82,10 @@ _Avoid_: Renderer capture
- A **Waited Run** may produce one **Run Completion**, time out for its caller, or be interrupted by **Session** exit.
- Caller timeout does not cancel the underlying **Run Completion**; it may still be observed later to keep internal completion bytes out of artifacts.
- After **Session** exit, an unobserved **Run Completion** can no longer arrive.
- A **Release Prep Workflow** produces local, reviewable release changes before any release tag exists.
- A **Release Finalization Step** happens after the **Release Prep Workflow** has landed on the default branch.
- A **Release Finalization Step** produces the release tag consumed by the **Publish Pipeline**.
- A **Publish Pipeline** starts from the release tag and owns release packaging and publishing.

## Example dialogue

Expand Down
173 changes: 118 additions & 55 deletions docs/RELEASE-PROCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,101 +87,112 @@ That command produces the same tarball, checksum, and metadata shape that the Gi

## Release flow overview

Because `main` is pull-request-only, the correct release flow is:
Because `main` is pull-request-only, the release process has three named parts:

1. create a release branch from `main`,
2. bump the version **without creating a tag yet**,
3. open and merge a PR,
4. tag the merged `main` commit,
5. let the `Release` workflow publish the GitHub Release assets and npm package.
1. **Release Prep Workflow**: prepare a reviewable release branch and one local release-prep commit.
2. **Release Finalization Step**: after the release PR merges, create and push the matching annotated release tag from clean, synced `main`.
3. **Publish Pipeline**: let the tag-triggered `Release` workflow publish GitHub assets and npm.

Do **not** run `npm version ...` on `main` and then push `HEAD --follow-tags`; GitHub will reject the protected-branch push but still accept the tag, which can start a release from an unmerged commit.

The primary commands are project-owned wrappers:

```bash
npm run release:prep -- --version <exact-semver> --changelog local|ci
npm run release:finalize
```

`release-it` is an implementation detail of the prep command only. Do not call raw `release-it` for agent-tty releases.

## Prepare the version-bump PR

Start from an up-to-date `main` checkout:
Start from a clean, up-to-date `main` checkout:

```bash
git checkout main
git pull origin main
```

### Stable release examples
Choose the exact release version. Increment aliases such as `patch`, `prepatch`, and `prerelease` are intentionally not part of the first scripted workflow; pass the exact semantic version instead.

Create a release branch and bump the version **without tagging**:
Stable release example:

```bash
git switch -c release/0.1.1
npm version patch --no-git-tag-version
npm run release:prep -- --version 0.1.1 --changelog ci
```

You can also choose the exact stable version explicitly:
Prerelease example:

```bash
npm version 0.1.1 --no-git-tag-version
npm run release:prep -- --version 0.1.1-beta.0 --changelog ci
```

### Prerelease examples
Versions containing a hyphen, such as `-beta.0` or `-rc.0`, are published by the workflow as GitHub prereleases and published to the matching npm dist-tag (`beta`, `rc`, and so on).

### Changelog mode

First beta on the next patch line:
Use `--changelog ci` for the default maintainer path. The prep commit will contain only `package.json` and `package-lock.json`; the `Release Changelog` workflow will update `CHANGELOG.md` on the release branch when needed.

```bash
git switch -c release/0.1.1-beta.0
npm version prepatch --preid beta --no-git-tag-version
npm run release:prep -- --version <version> --changelog ci
```

Next beta on the same line:
Use `--changelog local` only when you want to inspect the Communique changelog before opening the PR and have the required local credentials/tooling available:

```bash
npm version prerelease --preid beta --no-git-tag-version
npm run release:prep -- --version <version> --changelog local
```

Release candidate with an exact version:
Local changelog generation requires either `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`; when using only `OPENAI_API_KEY`, set `COMMUNIQUE_MODEL`. It also requires `communique` on `PATH` and GitHub API auth through `GITHUB_TOKEN` or an authenticated `gh` session. If those prerequisites are unavailable, rerun with `--changelog ci`.

Add `--verify` when you want the prep script to run the full local validation bar after creating the release-prep commit:

```bash
npm version 0.1.1-rc.0 --no-git-tag-version
npm run release:prep -- --version <version> --changelog ci --verify
```

Versions containing a hyphen, such as `-beta.0` or `-rc.0`, are published by the workflow as GitHub prereleases and published to the matching npm dist-tag (`beta`, `rc`, and so on).
The prep script validates release-specific invariants, creates `release/<version>` locally, updates the version files through pinned release-it configuration, optionally updates `CHANGELOG.md`, stages only allowlisted release-prep files, and creates exactly one commit:

### Open the release PR
```text
chore(release): <version>
```

Release branches named `release/*` are watched by the `Release Changelog` workflow. The default path is to commit only the version bump, then let that workflow add `CHANGELOG.md` if needed:
It does not push the branch or open the pull request. After it succeeds, push and open the PR with the commands printed by the script:

```bash
git add package.json package-lock.json
git commit -m "chore(release): <version>"
git push -u origin release/<version>
gh pr create --base main --head release/<version> --title "chore(release): <version>"
```

When `package.json` changes the package version and `CHANGELOG.md` does not already contain that version, the workflow runs:
Release branches named `release/*` are watched by the `Release Changelog` workflow. When `package.json` changes the package version and `CHANGELOG.md` does not already contain that version, the workflow runs:

```bash
communique generate "v<version>" --changelog --repo coder/agent-tty
```

and commits the resulting `CHANGELOG.md` update back to the release branch.
When it pushes that bot commit, it dispatches the CI and skill-validation
workflows for the updated release branch so protected-branch checks can run
against the new head commit.
and commits the resulting `CHANGELOG.md` update back to the release branch. When it pushes that bot commit, it dispatches the CI and skill-validation workflows for the updated release branch so protected-branch checks can run against the new head commit.

### Manual prep fallback

If you want to inspect or update the changelog before opening the PR, run the same command locally after `npm version ... --no-git-tag-version` and include `CHANGELOG.md` in the release commit instead:
If the scripted prep path is blocked, use the manual fallback only from a clean, up-to-date `main` checkout:

```bash
VERSION=$(node --input-type=module -e "import pkg from './package.json' with { type: 'json' }; process.stdout.write(pkg.version)")
communique generate "v${VERSION}" --changelog --repo coder/agent-tty
git add package.json package-lock.json CHANGELOG.md
git commit -m "chore(release): ${VERSION}"
git switch -c release/<version>
npm version <version> --no-git-tag-version
git add package.json package-lock.json
git commit -m "chore(release): <version>"
git push -u origin release/<version>
gh pr create --base main --head release/<version> --title "chore(release): <version>"
```

After committing either the default version bump or the local changelog variant:
For the local changelog variant, run Communique after `npm version ... --no-git-tag-version` and include `CHANGELOG.md` in the same commit:

```bash
git push -u origin <release-branch>
gh pr create --base main --head <release-branch> --title "chore(release): <version>"
communique generate "v<version>" --changelog --repo coder/agent-tty
git add package.json package-lock.json CHANGELOG.md
git commit -m "chore(release): <version>"
```

Run the normal PR checks, get approval as needed, and merge the PR.

## Wait for CI and merge the release PR

For interactive use, `gh pr checks <pr-number> --watch` is fine. For automation or agent-driven release work, prefer inspecting the workflow run directly so you can wait on structured `status` and `conclusion` fields instead of parsing live terminal refresh output.
Expand All @@ -204,22 +215,22 @@ Use `--admin` sparingly and only after confirming the required release checks su

## Tag the merged `main` commit

After the release PR has merged:
After the release PR has merged, use the Release Finalization Step from clean, synced `main`:

```bash
git checkout main
git pull origin main
git tag -a vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.Z
npm run release:finalize
```

For prereleases, use the full prerelease tag name, for example:
Add `--verify` when you want to run the full local validation bar immediately before tagging:

```bash
git tag -a v0.1.1-beta.0 -m "v0.1.1-beta.0"
git push origin v0.1.1-beta.0
npm run release:finalize -- --verify
```

The finalize script verifies that `package.json` and `package-lock.json` agree, derives the release tag as `v${package.json.version}`, rejects pre-existing local or remote tags, creates an annotated tag, and pushes only that tag.

The tag must match `package.json` exactly:

- `package.json`: `0.1.1`
Expand All @@ -230,6 +241,16 @@ or:
- `package.json`: `0.1.1-beta.0`
- tag: `v0.1.1-beta.0`

### Manual tag fallback

If the scripted finalization path is blocked after the release PR has merged, run the equivalent manual commands from clean, synced `main`:

```bash
VERSION=$(node --input-type=module -e "import pkg from './package.json' with { type: 'json' }; process.stdout.write(pkg.version)")
git tag -a "v${VERSION}" -m "v${VERSION}"
git push origin "v${VERSION}"
```

### GitHub CLI alternative: create the tag and release in one step

If you already know the merged `main` commit SHA and want GitHub to create the tag and release together, this also works:
Expand Down Expand Up @@ -339,24 +360,66 @@ npm install -g --prefix "$INSTALL_PREFIX" "$DOWNLOAD_DIR/$RELEASE_TGZ"
For private releases, authenticated download is the expected verification route.
If you are testing a public release and the direct asset URL is reachable in your environment, you can also verify the hosted install path directly with `npm install -g <release-asset-url>`.

## Recover from an accidental tag push
## Failure and recovery

If you accidentally push a release tag before the version-bump PR is merged:
### Release prep fails after creating a branch

1. cancel the in-progress workflow run,
2. delete the remote tag,
3. delete the local tag,
4. redo the release through the PR-first flow above.
If `npm run release:prep` fails after creating `release/<version>`, inspect the local branch before deleting anything:

Example cleanup:
```bash
git status
git log --oneline --decorate --max-count=5
```

If there is no work worth preserving, return to `main`, delete the local release branch, and rerun from clean, synced `main`:

```bash
git switch main
git branch -D release/<version>
git pull origin main
npm run release:prep -- --version <version> --changelog ci
```

### Release finalization fails to push the tag

If `npm run release:finalize` creates the local tag but fails before pushing it, delete the local tag, fix the underlying issue, and retry from clean, synced `main`:

```bash
git tag -d v<version>
npm run release:finalize
```

### Release finalization pushes a tag but the Release workflow fails before publishing
Comment thread
ThomasK33 marked this conversation as resolved.

If `npm run release:finalize` pushes the tag but the workflow fails before any GitHub Release or npm publish, fix the underlying issue on `main`. Delete and recreate the failed tag only if maintainers explicitly decide it is safe, and document the action.

Example tag cleanup, only after that explicit decision:

```bash
gh run cancel <run-id>
git push origin :refs/tags/vX.Y.Z
git tag -d vX.Y.Z
```

Then create or update the release branch PR, merge it, and tag the merged `main` commit.
Then rerun the Release Finalization Step from clean, synced `main`.

### npm published but GitHub Release or verification fails

If npm publish succeeds, never reuse the same version, even if later GitHub Release asset creation or verification fails. Repair forward with a new version, or complete missing release assets manually according to maintainer policy.

### GitHub Release exists but npm publish fails

If the GitHub Release exists but npm publish fails, treat the release as partial. Verify which assets and npm state exist, then follow maintainer policy before deleting assets, deleting tags, or retrying publish automation.

### Accidental tag before merge

If a release tag is accidentally pushed before the version-bump PR is merged, cancel the in-progress workflow, delete the remote tag, delete the local tag, and redo the release through the PR-first flow above.

```bash
gh run cancel <run-id>
git push origin :refs/tags/vX.Y.Z
git tag -d vX.Y.Z
```

## Proof expectations

Expand Down
29 changes: 29 additions & 0 deletions docs/adr/0001-use-release-it-only-for-release-prep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
status: accepted
---

# Use release-it only for release prep

## Context

agent-tty already has a tag-triggered publish pipeline in `.github/workflows/release.yml`. That pipeline validates the release tag against `package.json`, checks that the tagged commit is reachable from `main`, runs CI, creates one release tarball, uploads checksum assets, generates Communique release notes, creates or updates the GitHub Release, and publishes the same tarball to npm via trusted publishing/OIDC.

The manual release-prep path still had friction: maintainers had to create the release branch, run `npm version ... --no-git-tag-version`, optionally generate a changelog, and remember not to tag or publish before the PR merged.

## Decision

Use a pinned `release-it` dependency only behind project-specific Release Prep Workflow commands. The release-it config disables commit, tag, push, npm publish, GitHub Release, and GitLab Release side effects. The wrapper script owns repository-specific guardrails, branch creation, changelog mode, allowlisted staging, and the single local release-prep commit.

This is an intentional dependency tradeoff. For the first slice, release-it is only the pinned version-file engine, so a repo-owned `npm version <version> --no-git-tag-version` wrapper would be functionally smaller. We still keep release-it because the release-prep command is the project boundary: it can later grow exact release-it-supported version calculation features, while the wrapper and config keep finalization and publish side effects outside release-it. If the dependency cost stops being worth that future option, the project-owned command surface can keep the same behavior and swap the version-file engine back to plain `npm version`.

The Release Finalization Step does not use release-it. It uses repository-specific checks plus plain `git tag -a` and `git push origin <tag>` after the release-prep PR has landed on `main`.

The existing Publish Pipeline remains authoritative for packaging, GitHub Release assets, release notes, npm trusted publishing, and prerelease dist-tags.

## Consequences

- Maintainers run `npm run release:prep` and `npm run release:finalize`, not raw `release-it` commands.
- Release prep is non-interactive, exact-version-first, and local-only in the first slice.
- Release prep can choose `--changelog local` for a reviewed local Communique changelog or `--changelog ci` to let the release-changelog workflow update `CHANGELOG.md` on the release branch.
- Release finalization can only tag clean, synced `main` after the prep PR lands.
- npm publishing remains impossible from local release-prep tooling.
1 change: 1 addition & 0 deletions dogfood/20260429-release-it-prep/agent-tty-home.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/tmp/tmp.7ayxwfKSLk
Loading
Loading