From 368a7dd2d34782af426381483a9a21bbd7f922dc Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Wed, 18 Mar 2026 13:56:59 -0400 Subject: [PATCH 1/5] Rework release process --- .../pr_body_template.txt | 6 +- .github/workflows/check-release-pr.yaml | 33 ++-- .../workflows/close-release-pr-on-tag.yaml | 59 +++++++ .github/workflows/create-release-branch.yaml | 90 +++++++++++ ...se-pr.yaml => create-version-bump-pr.yaml} | 60 +++---- .../workflows/merge-release-pr-on-tag.yaml | 148 ------------------ .github/workflows/minor-release.yaml | 4 +- .github/workflows/patch-release.yaml | 5 +- .github/workflows/update-release-pr.yaml | 22 +-- Justfile | 19 ++- RELEASE.md | 44 ++++-- 11 files changed, 249 insertions(+), 241 deletions(-) create mode 100644 .github/workflows/close-release-pr-on-tag.yaml create mode 100644 .github/workflows/create-release-branch.yaml rename .github/workflows/{create-release-pr.yaml => create-version-bump-pr.yaml} (57%) delete mode 100644 .github/workflows/merge-release-pr-on-tag.yaml diff --git a/.github/actions/generate-release-pr-body/pr_body_template.txt b/.github/actions/generate-release-pr-body/pr_body_template.txt index e63e7c2041f7..7d57c86a1835 100644 --- a/.github/actions/generate-release-pr-body/pr_body_template.txt +++ b/.github/actions/generate-release-pr-body/pr_body_template.txt @@ -7,7 +7,11 @@ Push the release tag to trigger the release: git fetch && git tag v{{VERSION}} origin/release/{{VERSION}} git push origin v{{VERSION}} ``` -This PR will auto-merge once the tag is pushed. +The tag push will trigger the release build. This PR will be automatically closed. + +## Cherry-Picks + +If you need to include additional fixes, cherry-pick them into the `release/{{VERSION}}` branch before tagging. ## Important Notes diff --git a/.github/workflows/check-release-pr.yaml b/.github/workflows/check-release-pr.yaml index 711ad7ab073e..a359263a0664 100644 --- a/.github/workflows/check-release-pr.yaml +++ b/.github/workflows/check-release-pr.yaml @@ -18,7 +18,7 @@ jobs: ref: ${{ github.head_ref }} fetch-depth: 0 - - name: Check all PR commits exist in main + - name: Check all PR commits are empty or cherry-picked from main run: | git fetch origin main @@ -39,19 +39,26 @@ jobs: exit 0 fi - COMMIT_COUNT=$(echo "$MISSING_COMMITS" | wc -l) - FIRST_COMMIT=$(git rev-list --reverse HEAD ^origin/main | head -1) + # Check if all unique commits are empty commits (release branch markers) + ALL_EMPTY=true + for commit in $MISSING_COMMITS; do + if ! git diff-tree --no-commit-id --name-only -r "$commit" | head -1 | grep -q '^$'; then + ALL_EMPTY=false + break + fi + done - if [ "$COMMIT_COUNT" -eq 1 ] && echo "$MISSING_COMMITS" | grep -q "$FIRST_COMMIT"; then - echo "✅ Only version bump commit is unique" - git log --oneline -1 "$FIRST_COMMIT" - else - echo "❌ Found commits that should exist in main:" + if [ "$ALL_EMPTY" = true ]; then + echo "✅ Only empty commits (release branch markers) are unique" for commit in $MISSING_COMMITS; do - if [ "$commit" != "$FIRST_COMMIT" ]; then - git log --oneline -1 "$commit" - fi + git log --oneline -1 "$commit" done - echo "Make sure commits have equivalents in main. If you've since updated main, re-run this job" - exit 1 + exit 0 fi + + echo "❌ Found commits with changes that don't exist in main:" + for commit in $MISSING_COMMITS; do + git log --oneline -1 "$commit" + done + echo "Cherry-pick these commits into main first, or re-run this job after updating main." + exit 1 diff --git a/.github/workflows/close-release-pr-on-tag.yaml b/.github/workflows/close-release-pr-on-tag.yaml new file mode 100644 index 000000000000..37c5305def96 --- /dev/null +++ b/.github/workflows/close-release-pr-on-tag.yaml @@ -0,0 +1,59 @@ +name: Close release PR on tag push + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: + actions: write + contents: write + pull-requests: write + +jobs: + close-release-pr: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Extract version from tag + id: version + env: + TAG: ${{ github.ref_name }} + run: | + VERSION=${TAG#v} + BRANCH="release/${VERSION}" + + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "branch=${BRANCH}" >> $GITHUB_OUTPUT + + echo "Tag: ${TAG}" + echo "Version: ${VERSION}" + echo "Expected branch: ${BRANCH}" + + - name: Find and close matching PR + env: + GH_TOKEN: ${{ github.token }} + BRANCH: ${{ steps.version.outputs.branch }} + TAG: ${{ steps.version.outputs.tag }} + VERSION: ${{ steps.version.outputs.version }} + run: | + PR_NUMBER=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty') + + if [ -z "$PR_NUMBER" ]; then + echo "ℹ️ No open PR found for branch: $BRANCH (may already be closed)" + else + echo "✅ Found PR #$PR_NUMBER for branch: $BRANCH" + gh pr close "$PR_NUMBER" --comment "Release $TAG has been tagged and pushed. Closing this PR (version bump was already merged to main)." + echo "✅ Closed PR #$PR_NUMBER" + fi + + - name: Trigger patch release + env: + BRANCH: ${{ steps.version.outputs.branch }} + GH_TOKEN: ${{ github.token }} + run: | + gh workflow run patch-release.yaml \ + --field target_branch=$BRANCH diff --git a/.github/workflows/create-release-branch.yaml b/.github/workflows/create-release-branch.yaml new file mode 100644 index 000000000000..8e1ce2cbc51e --- /dev/null +++ b/.github/workflows/create-release-branch.yaml @@ -0,0 +1,90 @@ +name: Create Release Branch + +on: + pull_request: + types: + - closed + branches: + - main + - release/** + +permissions: + contents: write + pull-requests: write + +jobs: + create-release-branch: + runs-on: ubuntu-latest + if: > + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.head.ref, 'version-bump/') + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 + + - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 + - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 + + - name: Extract version and base branch + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=$(just get-tag-version) + BASE_BRANCH="${{ github.event.pull_request.base.ref }}" + echo "version=$VERSION" >> $GITHUB_ENV + echo "base_branch=$BASE_BRANCH" >> $GITHUB_ENV + echo "Version: $VERSION" + echo "Base branch: $BASE_BRANCH" + + # Get prior version from the latest GitHub release + PRIOR_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') + if [[ -z "$PRIOR_TAG" || "$PRIOR_TAG" == "null" ]]; then + echo "No prior release found, using first commit" + PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV + echo "Prior ref: $PRIOR_TAG" + + - name: Create release branch + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + BRANCH_NAME="release/${{ env.version }}" + git switch -c "$BRANCH_NAME" + + # Add an empty commit so GitHub can represent this as a PR + git commit --allow-empty --message "chore(release): open release branch for ${{ env.version }}" + + git push origin "$BRANCH_NAME" + + HEAD_REF=$(git rev-parse HEAD) + echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV + echo "head_ref=$HEAD_REF" >> $GITHUB_ENV + + - name: Generate release notes + uses: ./.github/actions/generate-release-pr-body + with: + version: ${{ env.version }} + head_ref: ${{ env.head_ref }} + prior_ref: ${{ env.prior_ref }} + + - name: Create Release PR + run: | + PR_URL=$(gh pr create \ + -B "${{ env.base_branch }}" \ + -H "${{ env.branch_name }}" \ + --title "chore(release): release version ${{ env.version }}" \ + --body-file pr_body.txt) + echo "pr_url=$PR_URL" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Post release checklist comment + run: | + sed 's/{{VERSION}}/${{ env.version }}/g' RELEASE_CHECKLIST.md > checklist_comment.md + gh pr comment "${{ env.pr_url }}" --body-file checklist_comment.md + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create-release-pr.yaml b/.github/workflows/create-version-bump-pr.yaml similarity index 57% rename from .github/workflows/create-release-pr.yaml rename to .github/workflows/create-version-bump-pr.yaml index 4daf75ad7ba8..551ecf6dd3b2 100644 --- a/.github/workflows/create-release-pr.yaml +++ b/.github/workflows/create-version-bump-pr.yaml @@ -1,14 +1,10 @@ -name: Release Workflow +name: Create Version Bump PR on: workflow_call: inputs: - bump_type: - description: 'Type of version bump (minor or patch)' - required: true - type: string target_branch: - description: 'Target branch for the pull request' + description: 'Branch to bump version on (main for minor, release/* for patch)' required: false type: string default: 'main' @@ -31,34 +27,26 @@ permissions: pull-requests: write jobs: - create-release: + create-version-bump: runs-on: ubuntu-latest env: - BUMP_TYPE: ${{ inputs.bump_type }} TARGET_BRANCH: ${{ inputs.target_branch }} steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ inputs.target_branch }} - fetch-depth: 0 # to generate complete release log + fetch-depth: 0 - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 - - name: Validate input and set old version - run: | - if [[ "$BUMP_TYPE" != "minor" && "$BUMP_TYPE" != "patch" ]]; then - echo "Error: bump_type must be 'minor' or 'patch'" - exit 1 - fi - - name: install dependencies run: | sudo apt update -y sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev - - name: create release branch + - name: Compute version and create bump branch env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} @@ -67,52 +55,40 @@ jobs: XAI_API_KEY: ${{ secrets.XAI_API_KEY }} TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} run: | - PRIOR_VERSION=$(just get-tag-version) - if [[ "$BUMP_TYPE" == "minor" ]]; then + # Determine bump type from target branch + if [[ "$TARGET_BRANCH" == "main" ]]; then + BUMP_TYPE="minor" VERSION=$(just get-next-minor-version) else + BUMP_TYPE="patch" VERSION=$(just get-next-patch-version) fi - echo "prior_ref=v$PRIOR_VERSION" >> $GITHUB_ENV + echo "bump_type=$BUMP_TYPE" >> $GITHUB_ENV echo "version=$VERSION" >> $GITHUB_ENV - echo "Version: $VERSION" + echo "Version: $VERSION ($BUMP_TYPE bump on $TARGET_BRANCH)" git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" + BRANCH_NAME="version-bump/$VERSION" + git switch -c "$BRANCH_NAME" + just prepare-release $VERSION - BRANCH_NAME=$(git branch --show-current) - HEAD_REF=$(git rev-parse HEAD) + echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV - echo "head_ref=$HEAD_REF" >> $GITHUB_ENV - echo "Branch: $BRANCH_NAME" - - name: push release branch + - name: Push bump branch run: | git push origin "${{ env.branch_name }}" - - name: Generate release notes - uses: ./.github/actions/generate-release-pr-body - with: - version: ${{ env.version }} - head_ref: ${{ env.head_ref }} - prior_ref: ${{ env.prior_ref }} - - name: Create Pull Request run: | PR_URL=$(gh pr create \ -B "$TARGET_BRANCH" \ -H "${{ env.branch_name }}" \ - --title "chore(release): release version ${{ env.version }} ($BUMP_TYPE)" \ - --body-file pr_body.txt) + --title "chore(release): bump version to ${{ env.version }} (${{ env.bump_type }})" \ + --body "Bumps version to **${{ env.version }}**. Merging this PR will trigger creation of the \`release/${{ env.version }}\` branch and release PR.") echo "pr_url=$PR_URL" >> $GITHUB_ENV env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Post release checklist comment - run: | - sed 's/{{VERSION}}/${{ env.version }}/g' RELEASE_CHECKLIST.md > checklist_comment.md - gh pr comment "${{ env.pr_url }}" --body-file checklist_comment.md - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge-release-pr-on-tag.yaml b/.github/workflows/merge-release-pr-on-tag.yaml deleted file mode 100644 index c0709fe0640f..000000000000 --- a/.github/workflows/merge-release-pr-on-tag.yaml +++ /dev/null @@ -1,148 +0,0 @@ -name: Merge release PR on tag push - -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - -permissions: - actions: write - contents: write - pull-requests: write - checks: read - -jobs: - trigger-patch-release: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Extract version from tag - id: version - env: - TAG: ${{ github.ref_name }} - run: | - VERSION=${TAG#v} - BRANCH="release/${VERSION}" - - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "branch=${BRANCH}" >> $GITHUB_OUTPUT - - echo "Tag: ${TAG}" - echo "Version: ${VERSION}" - echo "Expected branch: ${BRANCH}" - - - name: Find matching PR - id: find_pr - env: - GH_TOKEN: ${{ github.token }} - BRANCH: ${{ steps.version.outputs.branch }} - run: | - PR_NUMBER=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty') - - if [ -z "$PR_NUMBER" ]; then - echo "❌ No open PR found for branch: $BRANCH" - echo "pr_found=false" >> $GITHUB_OUTPUT - exit 1 - else - echo "✅ Found PR #$PR_NUMBER for branch: $BRANCH" - echo "pr_found=true" >> $GITHUB_OUTPUT - echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT - fi - - - name: Get PR details and check status - if: steps.find_pr.outputs.pr_found == 'true' - id: pr_status - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ steps.find_pr.outputs.pr_number }} - run: | - PR_DATA=$(gh pr view $PR_NUMBER --json title,headRefName,baseRefName,mergeable,statusCheckRollup) - - TITLE=$(echo "$PR_DATA" | jq -r '.title') - HEAD_BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName') - BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.baseRefName') - MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable') - - echo "PR Title: $TITLE" - echo "Head Branch: $HEAD_BRANCH" - echo "Base Branch: $BASE_BRANCH" - echo "Mergeable: $MERGEABLE" - - if [ "$MERGEABLE" != "MERGEABLE" ]; then - echo "❌ PR is not in a mergeable state: $MERGEABLE" - echo "can_merge=false" >> $GITHUB_OUTPUT - echo "merge_reason=PR is not mergeable (state: $MERGEABLE)" >> $GITHUB_OUTPUT - exit 0 - fi - - STATUS_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[]? | select(.conclusion != null) | "\(.context): \(.conclusion)"') - FAILED_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[]? | select(.conclusion == "FAILURE" or .conclusion == "CANCELLED" or .conclusion == "TIMED_OUT") | .context') - PENDING_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[]? | select(.conclusion == null) | .context') - - echo "Status checks:" - if [ -n "$STATUS_CHECKS" ]; then - echo "$STATUS_CHECKS" - else - echo "No status checks found" - fi - - if [ -n "$FAILED_CHECKS" ]; then - echo "❌ Failed checks found:" - echo "$FAILED_CHECKS" - echo "can_merge=false" >> $GITHUB_OUTPUT - echo "merge_reason=Some checks are failing" >> $GITHUB_OUTPUT - exit 0 - fi - - if [ -n "$PENDING_CHECKS" ]; then - echo "⏳ Pending checks found:" - echo "$PENDING_CHECKS" - echo "can_merge=false" >> $GITHUB_OUTPUT - echo "merge_reason=Some checks are still pending" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "✅ All checks are passing and PR is ready to merge" - echo "can_merge=true" >> $GITHUB_OUTPUT - - - name: Get branch SHA before merge - if: steps.find_pr.outputs.pr_found == 'true' && steps.pr_status.outputs.can_merge == 'true' - id: branch_info - env: - BRANCH: ${{ steps.version.outputs.branch }} - run: | - git fetch origin "$BRANCH" - - BRANCH_SHA=$(git rev-parse "origin/$BRANCH") - echo "branch_sha=$BRANCH_SHA" >> $GITHUB_OUTPUT - echo "Branch SHA: $BRANCH_SHA" - - - name: Merge PR - if: steps.find_pr.outputs.pr_found == 'true' && steps.pr_status.outputs.can_merge == 'true' - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ steps.find_pr.outputs.pr_number }} - VERSION: ${{ steps.version.outputs.version }} - TAG: ${{ steps.version.outputs.tag }} - run: | - gh pr merge $PR_NUMBER --squash --delete-branch --subject "Release $VERSION" --body "Auto-merged release PR after tag $TAG was pushed" - - - name: Restore branch - if: steps.find_pr.outputs.pr_found == 'true' && steps.pr_status.outputs.can_merge == 'true' - env: - BRANCH: ${{ steps.version.outputs.branch }} - BRANCH_SHA: ${{ steps.branch_info.outputs.branch_sha }} - run: | - git checkout -b "$BRANCH" "$BRANCH_SHA" - git push origin "$BRANCH" - - - name: Trigger patch release - env: - BRANCH: ${{ steps.version.outputs.branch }} - GH_TOKEN: ${{ github.token }} - run: | - gh workflow run patch-release.yaml \ - --field target_branch=$BRANCH diff --git a/.github/workflows/minor-release.yaml b/.github/workflows/minor-release.yaml index 8b81026133a7..106b41c22e04 100644 --- a/.github/workflows/minor-release.yaml +++ b/.github/workflows/minor-release.yaml @@ -12,9 +12,7 @@ on: jobs: release: if: github.repository == 'block/goose' - uses: ./.github/workflows/create-release-pr.yaml - with: - bump_type: "minor" + uses: ./.github/workflows/create-version-bump-pr.yaml secrets: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} diff --git a/.github/workflows/patch-release.yaml b/.github/workflows/patch-release.yaml index c0a721711448..cb3d333e9334 100644 --- a/.github/workflows/patch-release.yaml +++ b/.github/workflows/patch-release.yaml @@ -8,15 +8,14 @@ on: workflow_dispatch: inputs: target_branch: - description: 'Target branch for hotfix' + description: 'Release branch for patch (e.g. release/1.25.0)' required: true type: string jobs: hotfix: - uses: ./.github/workflows/create-release-pr.yaml + uses: ./.github/workflows/create-version-bump-pr.yaml with: - bump_type: "patch" target_branch: ${{ inputs.target_branch }} secrets: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/.github/workflows/update-release-pr.yaml b/.github/workflows/update-release-pr.yaml index 65d690e8547d..f12d925ee3b8 100644 --- a/.github/workflows/update-release-pr.yaml +++ b/.github/workflows/update-release-pr.yaml @@ -19,12 +19,8 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: - fetch-depth: 0 # to generate complete release log - - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - ref: ${{ github.base_ref }} - path: './prior-version' + fetch-depth: 0 + fetch-tags: true - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 @@ -38,11 +34,17 @@ jobs: echo "version=$VERSION" >> $GITHUB_ENV echo "Version: $VERSION" - - name: Get prior version - working-directory: './prior-version' + - name: Get prior version from GitHub releases + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - PRIOR_VERSION=$(just get-tag-version) - echo "prior_ref=v$PRIOR_VERSION" >> $GITHUB_ENV + PRIOR_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') + if [[ -z "$PRIOR_TAG" || "$PRIOR_TAG" == "null" ]]; then + echo "No prior release found, using first commit" + PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV + echo "Prior ref: $PRIOR_TAG" - name: Generate release notes uses: ./.github/actions/generate-release-pr-body diff --git a/Justfile b/Justfile index 57e200d6b005..be455b845c02 100644 --- a/Justfile +++ b/Justfile @@ -306,20 +306,23 @@ get-next-minor-version: get-next-patch-version: @python -c "import sys; v=sys.argv[1].split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')" $(just get-tag-version) -# set cargo and app versions, must be semver -prepare-release version: +# update version numbers in all manifests +bump-version version: @just validate {{ version }} || exit 1 - - @git switch -c "release/{{ version }}" @uvx --from=toml-cli toml set --toml-path=Cargo.toml "workspace.package.version" {{ version }} - @cd ui/desktop && pnpm version {{ version }} --no-git-tag-version --allow-same-version - - # see --workspace flag https://doc.rust-lang.org/cargo/commands/cargo-update.html - # used to update Cargo.lock after we've bumped versions in Cargo.toml + # update Cargo.lock after bumping versions in Cargo.toml @cargo update --workspace @just set-openapi-version {{ version }} + +# rebuild canonical model registry and mapping report from models.dev +build-canonical-models: @cargo run --bin build_canonical_models + +# bump version, rebuild canonical models, and commit +prepare-release version: + @just bump-version {{ version }} + @just build-canonical-models @git add \ Cargo.toml \ Cargo.lock \ diff --git a/RELEASE.md b/RELEASE.md index 881ca6d290c5..f085798d23b6 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,28 +2,46 @@ You'll generally create one of two release types: a regular feature release (minor version bump like 1.20) or a bug-fixing patch release (patch version bump like 1.20.1). -Regular releases start on main, while patch releases start with an existing release tag. goose uses GitHub actions to automate the creation of release branches. The actual releases are triggered by tags. -For bug-fixing releases, you will cherry-pick fixes into that branch, test, and then release from it. +goose uses GitHub actions to automate the release process. The actual releases are triggered by tags. ## Minor version releases -These are typically done once per week. There is an [action](https://github.com/block/goose/actions/workflows/minor-release.yaml) that cuts the branch every Tuesday, but it can also be triggered manually. Commits from main can be cherry-picked into this branch as needed before release. +These are typically done once per week. The process has two automated phases: -To trigger the release, find [the corresponding PR](https://github.com/block/goose/pulls?q=is%3Apr+%22chore%28release%29%22+%22%28minor%29%22+author%3Aapp%2Fgithub-actions+) and follow the instructions in the PR description. +1. **Version bump PR** — An [action](https://github.com/block/goose/actions/workflows/minor-release.yaml) runs every Tuesday (or can be triggered manually) that creates a PR to bump the version on `main`. Review and merge this PR. + +2. **Release branch + PR** — When the version bump PR merges, automation creates a `release/` branch from `main` and opens a release PR with a QA checklist. + +From there: +- Test locally if you can (`just run-ui`) +- Cherry-pick any last-minute fixes into the release branch if needed +- Download and test the .zip from the release PR +- When ready, follow the instructions on the release PR to tag and release + +To trigger the release, find [the corresponding PR](https://github.com/block/goose/pulls?q=is%3Apr+%22chore%28release%29%22+author%3Aapp%2Fgithub-actions+) and follow the instructions in the PR description. ## Patch version releases -Minor and patch releases both trigger the creation of a branch for a follow-on patch release. These branches can be used to create patch releases, or can be safely ignored/closed. -You can cherry pick fixes into this branch. +When a minor release is tagged, automation creates a patch release branch ready for hotfixes. Cherry-pick fixes into this branch, then trigger a patch release. To trigger the release, find [the corresponding PR](https://github.com/block/goose/pulls?q=is%3Apr+%22chore%28release%29%22+%22%28patch%29%22+author%3Aapp%2Fgithub-actions+) and follow the instructions in the PR description. - ## High level release flow: -* check out and cherry-pick (if needed) changes to the branch you are going to release (eg the patch branch) -* Test locally if you can (just run-ui) -* Push changes to that branch, wait for build -* Download and test the .zip from the release PR -* If happy, follow the instructions on the release PR to tag and release (tagging will trigger the real release from there) - +``` +minor-release (cron/manual) + │ + ▼ +version-bump PR → main (merge this) + │ + ▼ (on merge) +release/ branch created from main +release PR opened (for QA, cherry-picks, testing) + │ + ▼ (when ready) +git tag v origin/release/ && git push origin v + │ + ├─► release PR auto-closed (no merge needed) + ├─► release.yml builds & publishes + └─► next patch release branch auto-created +``` From 643bc23f9879f5bed3f2a7086389b3559c9f6c0f Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Wed, 18 Mar 2026 16:14:29 -0400 Subject: [PATCH 2/5] Immediately create patch release PR --- .github/workflows/create-release-branch.yaml | 1 - .github/workflows/create-version-bump-pr.yaml | 27 +----- .github/workflows/patch-release.yaml | 93 ++++++++++++++++--- RELEASE.md | 7 +- 4 files changed, 91 insertions(+), 37 deletions(-) diff --git a/.github/workflows/create-release-branch.yaml b/.github/workflows/create-release-branch.yaml index 8e1ce2cbc51e..c75ffebba5e8 100644 --- a/.github/workflows/create-release-branch.yaml +++ b/.github/workflows/create-release-branch.yaml @@ -6,7 +6,6 @@ on: - closed branches: - main - - release/** permissions: contents: write diff --git a/.github/workflows/create-version-bump-pr.yaml b/.github/workflows/create-version-bump-pr.yaml index 551ecf6dd3b2..4def147f8dfc 100644 --- a/.github/workflows/create-version-bump-pr.yaml +++ b/.github/workflows/create-version-bump-pr.yaml @@ -2,12 +2,6 @@ name: Create Version Bump PR on: workflow_call: - inputs: - target_branch: - description: 'Branch to bump version on (main for minor, release/* for patch)' - required: false - type: string - default: 'main' secrets: ANTHROPIC_API_KEY: required: false @@ -29,13 +23,11 @@ permissions: jobs: create-version-bump: runs-on: ubuntu-latest - env: - TARGET_BRANCH: ${{ inputs.target_branch }} steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: - ref: ${{ inputs.target_branch }} + ref: main fetch-depth: 0 - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 @@ -55,18 +47,9 @@ jobs: XAI_API_KEY: ${{ secrets.XAI_API_KEY }} TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} run: | - # Determine bump type from target branch - if [[ "$TARGET_BRANCH" == "main" ]]; then - BUMP_TYPE="minor" - VERSION=$(just get-next-minor-version) - else - BUMP_TYPE="patch" - VERSION=$(just get-next-patch-version) - fi - - echo "bump_type=$BUMP_TYPE" >> $GITHUB_ENV + VERSION=$(just get-next-minor-version) echo "version=$VERSION" >> $GITHUB_ENV - echo "Version: $VERSION ($BUMP_TYPE bump on $TARGET_BRANCH)" + echo "Version: $VERSION (minor bump on main)" git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" @@ -85,9 +68,9 @@ jobs: - name: Create Pull Request run: | PR_URL=$(gh pr create \ - -B "$TARGET_BRANCH" \ + -B main \ -H "${{ env.branch_name }}" \ - --title "chore(release): bump version to ${{ env.version }} (${{ env.bump_type }})" \ + --title "chore(release): bump version to ${{ env.version }} (minor)" \ --body "Bumps version to **${{ env.version }}**. Merging this PR will trigger creation of the \`release/${{ env.version }}\` branch and release PR.") echo "pr_url=$PR_URL" >> $GITHUB_ENV env: diff --git a/.github/workflows/patch-release.yaml b/.github/workflows/patch-release.yaml index cb3d333e9334..181e0767aff5 100644 --- a/.github/workflows/patch-release.yaml +++ b/.github/workflows/patch-release.yaml @@ -1,4 +1,4 @@ -name: Create Patch Release PR +name: Create Patch Release permissions: contents: write @@ -13,14 +13,83 @@ on: type: string jobs: - hotfix: - uses: ./.github/workflows/create-version-bump-pr.yaml - with: - target_branch: ${{ inputs.target_branch }} - secrets: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} - XAI_API_KEY: ${{ secrets.XAI_API_KEY }} - TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} + create-patch-release: + runs-on: ubuntu-latest + env: + TARGET_BRANCH: ${{ inputs.target_branch }} + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 + - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 + + - name: install dependencies + run: | + sudo apt update -y + sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev + + - name: Compute version and create release branch + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + XAI_API_KEY: ${{ secrets.XAI_API_KEY }} + TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=$(just get-next-patch-version) + echo "version=$VERSION" >> $GITHUB_ENV + echo "Version: $VERSION (patch on $TARGET_BRANCH)" + + # Get prior version from the latest GitHub release + PRIOR_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') + if [[ -z "$PRIOR_TAG" || "$PRIOR_TAG" == "null" ]]; then + echo "No prior release found, using first commit" + PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV + echo "Prior ref: $PRIOR_TAG" + + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + BRANCH_NAME="release/$VERSION" + git switch -c "$BRANCH_NAME" + + just prepare-release $VERSION + + git push origin "$BRANCH_NAME" + + HEAD_REF=$(git rev-parse HEAD) + echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV + echo "head_ref=$HEAD_REF" >> $GITHUB_ENV + + - name: Generate release notes + uses: ./.github/actions/generate-release-pr-body + with: + version: ${{ env.version }} + head_ref: ${{ env.head_ref }} + prior_ref: ${{ env.prior_ref }} + + - name: Create Release PR + run: | + PR_URL=$(gh pr create \ + -B "$TARGET_BRANCH" \ + -H "${{ env.branch_name }}" \ + --title "chore(release): release version ${{ env.version }} (patch)" \ + --body-file pr_body.txt) + echo "pr_url=$PR_URL" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Post release checklist comment + run: | + sed 's/{{VERSION}}/${{ env.version }}/g' RELEASE_CHECKLIST.md > checklist_comment.md + gh pr comment "${{ env.pr_url }}" --body-file checklist_comment.md + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/RELEASE.md b/RELEASE.md index f085798d23b6..2f78f5ae3301 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -22,7 +22,7 @@ To trigger the release, find [the corresponding PR](https://github.com/block/goo ## Patch version releases -When a minor release is tagged, automation creates a patch release branch ready for hotfixes. Cherry-pick fixes into this branch, then trigger a patch release. +When a minor release is tagged, automation immediately creates the next patch release branch (e.g. `release/1.25.1` from `release/1.25.0`) with the version already bumped and a release PR open. Cherry-pick fixes into this branch, then tag when ready. To trigger the release, find [the corresponding PR](https://github.com/block/goose/pulls?q=is%3Apr+%22chore%28release%29%22+%22%28patch%29%22+author%3Aapp%2Fgithub-actions+) and follow the instructions in the PR description. @@ -43,5 +43,8 @@ git tag v origin/release/ && git push origin v │ ├─► release PR auto-closed (no merge needed) ├─► release.yml builds & publishes - └─► next patch release branch auto-created + └─► patch release/ branch + PR auto-created + │ + ▼ (cherry-pick fixes, then when ready) + git tag v origin/release/ && git push origin v ``` From 771f9c6cb63dec7f4986bfc2296a3cf0d9e4df45 Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Wed, 18 Mar 2026 16:31:48 -0400 Subject: [PATCH 3/5] pin to merge commit --- .github/workflows/create-release-branch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-release-branch.yaml b/.github/workflows/create-release-branch.yaml index c75ffebba5e8..999669a08ef6 100644 --- a/.github/workflows/create-release-branch.yaml +++ b/.github/workflows/create-release-branch.yaml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: - ref: ${{ github.event.pull_request.base.ref }} + ref: ${{ github.event.pull_request.merge_commit_sha }} fetch-depth: 0 - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 From 1db74ee45b6ce05ffc1a64799f43258f47f522dd Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Thu, 19 Mar 2026 09:54:18 -0400 Subject: [PATCH 4/5] better prior version detection --- .github/workflows/create-release-branch.yaml | 7 +++---- .github/workflows/patch-release.yaml | 7 +++---- .github/workflows/update-release-pr.yaml | 9 ++++----- Justfile | 18 ++++++++++++++++++ 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/.github/workflows/create-release-branch.yaml b/.github/workflows/create-release-branch.yaml index 999669a08ef6..4296fad1676a 100644 --- a/.github/workflows/create-release-branch.yaml +++ b/.github/workflows/create-release-branch.yaml @@ -37,10 +37,9 @@ jobs: echo "Version: $VERSION" echo "Base branch: $BASE_BRANCH" - # Get prior version from the latest GitHub release - PRIOR_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') - if [[ -z "$PRIOR_TAG" || "$PRIOR_TAG" == "null" ]]; then - echo "No prior release found, using first commit" + PRIOR_TAG=$(just get-prior-version "$VERSION") + if [[ -z "$PRIOR_TAG" ]]; then + echo "No prior version (first release), using first commit" PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) fi echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV diff --git a/.github/workflows/patch-release.yaml b/.github/workflows/patch-release.yaml index 181e0767aff5..c3aa5774131e 100644 --- a/.github/workflows/patch-release.yaml +++ b/.github/workflows/patch-release.yaml @@ -46,10 +46,9 @@ jobs: echo "version=$VERSION" >> $GITHUB_ENV echo "Version: $VERSION (patch on $TARGET_BRANCH)" - # Get prior version from the latest GitHub release - PRIOR_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') - if [[ -z "$PRIOR_TAG" || "$PRIOR_TAG" == "null" ]]; then - echo "No prior release found, using first commit" + PRIOR_TAG=$(just get-prior-version "$VERSION") + if [[ -z "$PRIOR_TAG" ]]; then + echo "No prior version (first release), using first commit" PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) fi echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV diff --git a/.github/workflows/update-release-pr.yaml b/.github/workflows/update-release-pr.yaml index f12d925ee3b8..4b53f74031d6 100644 --- a/.github/workflows/update-release-pr.yaml +++ b/.github/workflows/update-release-pr.yaml @@ -20,7 +20,6 @@ jobs: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 - fetch-tags: true - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 @@ -34,13 +33,13 @@ jobs: echo "version=$VERSION" >> $GITHUB_ENV echo "Version: $VERSION" - - name: Get prior version from GitHub releases + - name: Get prior version env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - PRIOR_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') - if [[ -z "$PRIOR_TAG" || "$PRIOR_TAG" == "null" ]]; then - echo "No prior release found, using first commit" + PRIOR_TAG=$(just get-prior-version "${{ env.version }}") + if [[ -z "$PRIOR_TAG" ]]; then + echo "No prior version (first release), using first commit" PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) fi echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV diff --git a/Justfile b/Justfile index be455b845c02..f3f82a2270aa 100644 --- a/Justfile +++ b/Justfile @@ -306,6 +306,24 @@ get-next-minor-version: get-next-patch-version: @python -c "import sys; v=sys.argv[1].split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')" $(just get-tag-version) +# derive the prior release tag from a version +# patch bump (e.g. 1.25.1): prior is v1.25.0 (deterministic) +# minor bump (e.g. 1.26.0): prior is highest v1.25.* GitHub release +get-prior-version version: + #!/usr/bin/env bash + IFS='.' read -r major minor patch <<< "{{ version }}" + if [[ "$patch" -gt 0 ]]; then + echo "v${major}.${minor}.$((patch - 1))" + elif [[ "$minor" -gt 0 ]]; then + prev_minor=$((minor - 1)) + prefix="v${major}.${prev_minor}." + best=$(gh release list --limit 100 --exclude-drafts --exclude-pre-releases \ + --json tagName --jq "[.[] | select(.tagName | startswith(\"${prefix}\"))][0].tagName") + if [[ -n "$best" && "$best" != "null" ]]; then + echo "$best" + fi + fi + # update version numbers in all manifests bump-version version: @just validate {{ version }} || exit 1 From deb3aaaf8bc9a8822dac4ffb59bc63807299e29d Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Thu, 19 Mar 2026 10:08:44 -0400 Subject: [PATCH 5/5] address feedback --- .github/workflows/check-release-pr.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/check-release-pr.yaml b/.github/workflows/check-release-pr.yaml index a359263a0664..148b1bfbd57a 100644 --- a/.github/workflows/check-release-pr.yaml +++ b/.github/workflows/check-release-pr.yaml @@ -39,10 +39,9 @@ jobs: exit 0 fi - # Check if all unique commits are empty commits (release branch markers) ALL_EMPTY=true for commit in $MISSING_COMMITS; do - if ! git diff-tree --no-commit-id --name-only -r "$commit" | head -1 | grep -q '^$'; then + if [ -n "$(git diff-tree --no-commit-id --name-only -r "$commit")" ]; then ALL_EMPTY=false break fi