From 2368df1008b515e36cb39290db7ea4bfbbf970ea Mon Sep 17 00:00:00 2001 From: Erik Weathers Date: Tue, 21 Apr 2026 13:33:02 -0700 Subject: [PATCH 1/5] [release process update] add `force_republish` option & `release/` branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds a `force_republish` workflow input to recover from incomplete releases, and introduces `release/` branches so every release has a clean, reproducible artifact branch with the correct `Chart.yaml`. ### New: `force_republish` input A new boolean dispatch input (default `false`) allows re-running the release workflow for a version that previously failed partway through. - Validation requires the tag to already exist (errors if it doesn't — nothing to republish from) - The existing GitHub release and tag are deleted, then fully recreated from scratch - The existing `release/` branch is also deleted and recreated - Main is **not** updated — only the release branch is touched ### New: `release/` branches Every release now creates a dedicated `release/$CHART_VERSION` branch: - **Normal flow**: version bumps (services + chart) are committed and pushed to main as before, then `release/$CHART_VERSION` is branched off main's new HEAD and pushed. - **`force_republish` flow**: a fresh `release/$CHART_VERSION` is created from the SHA the broken tag pointed to, the `Chart.yaml` bump is re-applied (idempotent), and the branch is pushed. `gh release create` now uses `--target release/$CHART_VERSION` in both paths, so the git tag is always created from a commit guaranteed to have the correct `Chart.yaml` version. ### Other fixes - `fetch-tags: true` added to the checkout step so the tag existence check and `git rev-parse` in `force_republish` mode work correctly - `GH_TOKEN` added to the "Prepare release branch" step (needed for `gh release delete` / `gh release view`) ### Testing Tested against `erikdw/branch-based-releases-with-republish-support` via `gh workflow run`. **Normal flow (`0.0.2-test`)** Triggered with `version=0.0.2-test`, no `force_republish`. Verified: - `release/0.0.2-test` branch created and pushed - Chart.yaml bumped and pushed to main - Draft release created via `gh release create --target release/0.0.2-test` - Helm chart packaged successfully (`braintrust-0.0.2-test.tgz`) - Release published with correct notes **`force_republish` flow (`0.0.3-test`)** Set up broken state by manually creating tag `0.0.3-test` and a draft GitHub release, then triggered with `version=0.0.3-test`, `force_republish=true`. Verified: - Tag existence check passed, SHA captured (`8f0e5e2`) - Existing release and tag deleted via `gh release delete --cleanup-tag` - Fresh `release/0.0.3-test` branch created from the captured SHA - Chart.yaml bumped on the release branch (main not touched) - Release recreated, packaged, and published successfully --- .github/workflows/release.yml | 102 ++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f0d9fe..03a2d99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,11 @@ on: description: 'Lock onto Braintrust services version (e.g., 1.2.3)' required: false type: string + force_republish: + description: 'Re-publish an existing version' + required: false + type: boolean + default: false env: CHART_PATH: ./braintrust @@ -38,6 +43,7 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ steps.bot-token.outputs.token }} + fetch-tags: true - name: Configure Git run: | @@ -75,10 +81,19 @@ jobs: exit 1 fi - if git tag | grep -q "^$VERSION$"; then - echo "❌ Error: Version $VERSION already exists" - echo "Please use a different version number" - exit 1 + if [[ "${{ github.event.inputs.force_republish }}" != "true" ]]; then + if git tag | grep -q "^$VERSION$"; then + echo "❌ Error: Version $VERSION already exists" + echo "Please use a different version number" + exit 1 + fi + else + if ! git tag | grep -q "^$VERSION$"; then + echo "❌ Error: force_republish=true but tag $VERSION does not exist" + echo "There is no existing release to republish" + exit 1 + fi + echo "⚠️ force_republish=true — will republish from existing tag $VERSION" fi # Store the cleaned version for later use @@ -97,36 +112,79 @@ jobs: echo "SERVICES_VERSION=$SERVICES_VERSION" >> $GITHUB_ENV fi - - name: Update versions + - name: Prepare release branch run: | - git fetch origin main - git checkout main + if [[ "${{ github.event.inputs.force_republish }}" == "true" ]]; then + # Capture the SHA from the existing (broken) tag before we delete anything + RELEASE_SHA=$(git rev-parse $CHART_VERSION) + echo "ℹ️ Republishing from existing tag $CHART_VERSION @ $RELEASE_SHA" + + # Delete the existing GitHub release and tag + if gh release view $CHART_VERSION &>/dev/null; then + echo "⚠️ Deleting existing release and tag $CHART_VERSION" + gh release delete $CHART_VERSION --yes --cleanup-tag + else + echo "ℹ️ No existing GitHub release found — deleting tag directly" + git push origin :refs/tags/$CHART_VERSION + fi - # Update services versions if provided - if [ "$SERVICES_VERSION" != "" ]; then - ./lock_versions $SERVICES_VERSION - git add . + # Delete old release branch if it exists + if git ls-remote --exit-code origin refs/heads/release/$CHART_VERSION &>/dev/null; then + echo "⚠️ Deleting existing release branch release/$CHART_VERSION" + git push origin --delete release/$CHART_VERSION + fi + + # Create fresh release branch from the original tag's commit + git checkout -b release/$CHART_VERSION $RELEASE_SHA + + # Re-apply Chart version bump in case it was missing from the broken release + sed -i "s/^version: .*/version: $CHART_VERSION/" $CHART_PATH/Chart.yaml + git add $CHART_PATH/Chart.yaml if ! git diff --staged --quiet; then - git commit -m "Update Braintrust Services versions to $SERVICES_VERSION" + git commit -m "Update Chart version to $CHART_VERSION" else - echo "No changes to commit for services version update" + echo "ℹ️ Chart.yaml already at version $CHART_VERSION" fi - fi - # Update Chart version - sed -i "s/^version: .*/version: $CHART_VERSION/" $CHART_PATH/Chart.yaml - git add $CHART_PATH/Chart.yaml - if ! git diff --staged --quiet; then - git commit -m "Update Chart version to $CHART_VERSION" + git push origin release/$CHART_VERSION + else - echo "No changes to commit for Chart version update" - fi + git fetch origin main + git checkout main + + # Update services versions if provided + if [ "$SERVICES_VERSION" != "" ]; then + ./lock_versions $SERVICES_VERSION + git add . + if ! git diff --staged --quiet; then + git commit -m "Update Braintrust Services versions to $SERVICES_VERSION" + else + echo "No changes to commit for services version update" + fi + fi - git push origin main + # Update Chart version on main + sed -i "s/^version: .*/version: $CHART_VERSION/" $CHART_PATH/Chart.yaml + git add $CHART_PATH/Chart.yaml + if ! git diff --staged --quiet; then + git commit -m "Update Chart version to $CHART_VERSION" + else + echo "No changes to commit for Chart version update" + fi + + git push origin main + + # Create release branch from main's updated HEAD + git checkout -b release/$CHART_VERSION + git push origin release/$CHART_VERSION + fi + env: + GH_TOKEN: ${{ steps.bot-token.outputs.token }} - name: Create GitHub Release run: | gh release create $CHART_VERSION \ + --target release/$CHART_VERSION \ --draft \ --title "$CHART_VERSION" \ --generate-notes From f61e44adfd2bff0587a5a9c50a691b0cd043abaa Mon Sep 17 00:00:00 2001 From: Erik Weathers Date: Wed, 22 Apr 2026 11:32:38 -0700 Subject: [PATCH 2/5] address Codex review: detect incomplete release early and bail out --- .github/workflows/release.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03a2d99..0db46e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -152,6 +152,13 @@ jobs: git fetch origin main git checkout main + # Fail fast if release branch already exists — indicates a partial release + if git ls-remote --exit-code origin refs/heads/release/$CHART_VERSION &>/dev/null; then + echo "❌ Error: release/$CHART_VERSION already exists — this release may be partially complete" + echo "Use force_republish=true to redo it from scratch" + exit 1 + fi + # Update services versions if provided if [ "$SERVICES_VERSION" != "" ]; then ./lock_versions $SERVICES_VERSION From a7683ba7fa57fb646a34f7b168a0da3d4792b047 Mon Sep 17 00:00:00 2001 From: Erik Weathers Date: Wed, 22 Apr 2026 11:39:54 -0700 Subject: [PATCH 3/5] Address Codex review: Fix `force_republish` to apply services version lock - Apply `./lock_versions` on the release branch in `force_republish` mode when `services_version` is provided, matching the behavior of the normal flow --- .github/workflows/release.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0db46e4..14bd25f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,6 +137,17 @@ jobs: # Create fresh release branch from the original tag's commit git checkout -b release/$CHART_VERSION $RELEASE_SHA + # Re-apply services version lock if provided + if [ "$SERVICES_VERSION" != "" ]; then + ./lock_versions $SERVICES_VERSION + git add . + if ! git diff --staged --quiet; then + git commit -m "Update Braintrust Services versions to $SERVICES_VERSION" + else + echo "No changes to commit for services version update" + fi + fi + # Re-apply Chart version bump in case it was missing from the broken release sed -i "s/^version: .*/version: $CHART_VERSION/" $CHART_PATH/Chart.yaml git add $CHART_PATH/Chart.yaml From 84e4a2a9491453bf0fc5f0339048e178af31cf8f Mon Sep 17 00:00:00 2001 From: Erik Weathers Date: Wed, 22 Apr 2026 12:38:22 -0700 Subject: [PATCH 4/5] Address mdeeks PR review comments - Split release/tag deletion into independent checks in force_republish path; always delete the tag explicitly rather than only in the else branch - Use --delete syntax for tag deletion for consistency with branch deletion - Replace git tag | grep with git tag --list for exact tag matching (dots in semver are regex wildcards, not literals) --- .github/workflows/release.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14bd25f..e3fa005 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,13 +82,13 @@ jobs: fi if [[ "${{ github.event.inputs.force_republish }}" != "true" ]]; then - if git tag | grep -q "^$VERSION$"; then + if [[ -n "$(git tag --list "$VERSION")" ]]; then echo "❌ Error: Version $VERSION already exists" echo "Please use a different version number" exit 1 fi else - if ! git tag | grep -q "^$VERSION$"; then + if [[ -z "$(git tag --list "$VERSION")" ]]; then echo "❌ Error: force_republish=true but tag $VERSION does not exist" echo "There is no existing release to republish" exit 1 @@ -119,13 +119,16 @@ jobs: RELEASE_SHA=$(git rev-parse $CHART_VERSION) echo "ℹ️ Republishing from existing tag $CHART_VERSION @ $RELEASE_SHA" - # Delete the existing GitHub release and tag + # Delete the existing GitHub release if present if gh release view $CHART_VERSION &>/dev/null; then - echo "⚠️ Deleting existing release and tag $CHART_VERSION" - gh release delete $CHART_VERSION --yes --cleanup-tag - else - echo "ℹ️ No existing GitHub release found — deleting tag directly" - git push origin :refs/tags/$CHART_VERSION + echo "⚠️ Deleting existing release $CHART_VERSION" + gh release delete $CHART_VERSION --yes + fi + + # Always delete the tag explicitly + if git ls-remote --exit-code origin refs/tags/$CHART_VERSION &>/dev/null; then + echo "⚠️ Deleting existing tag $CHART_VERSION" + git push origin --delete refs/tags/$CHART_VERSION fi # Delete old release branch if it exists From 8028fd21078ad52cbfc9f9b646a39e0f6fb5445e Mon Sep 17 00:00:00 2001 From: Erik Weathers Date: Wed, 22 Apr 2026 14:53:26 -0700 Subject: [PATCH 5/5] Deduplicate release branch prep across force_republish and normal modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two paths previously each created the release branch, applied the services-version lock, bumped Chart.yaml, and pushed — with only the starting point and final push target differing. Collapse them: - Both modes compute a START_POINT (tag SHA for force_republish, origin/main for normal) and share a single `git checkout -b release/$CHART_VERSION $START_POINT`. - Services-version lock, Chart.yaml bump, and `git push origin release/$CHART_VERSION` are now shared. - Normal mode fast-forwards main to the release branch tip at the end (`git push origin release/$CHART_VERSION:main`) instead of committing to main first and branching from it. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 79 ++++++++++++++--------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3fa005..bd19ec8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,8 +116,8 @@ jobs: run: | if [[ "${{ github.event.inputs.force_republish }}" == "true" ]]; then # Capture the SHA from the existing (broken) tag before we delete anything - RELEASE_SHA=$(git rev-parse $CHART_VERSION) - echo "ℹ️ Republishing from existing tag $CHART_VERSION @ $RELEASE_SHA" + START_POINT=$(git rev-parse $CHART_VERSION) + echo "ℹ️ Republishing from existing tag $CHART_VERSION @ $START_POINT" # Delete the existing GitHub release if present if gh release view $CHART_VERSION &>/dev/null; then @@ -136,35 +136,13 @@ jobs: echo "⚠️ Deleting existing release branch release/$CHART_VERSION" git push origin --delete release/$CHART_VERSION fi - - # Create fresh release branch from the original tag's commit - git checkout -b release/$CHART_VERSION $RELEASE_SHA - - # Re-apply services version lock if provided - if [ "$SERVICES_VERSION" != "" ]; then - ./lock_versions $SERVICES_VERSION - git add . - if ! git diff --staged --quiet; then - git commit -m "Update Braintrust Services versions to $SERVICES_VERSION" - else - echo "No changes to commit for services version update" - fi - fi - - # Re-apply Chart version bump in case it was missing from the broken release - sed -i "s/^version: .*/version: $CHART_VERSION/" $CHART_PATH/Chart.yaml - git add $CHART_PATH/Chart.yaml - if ! git diff --staged --quiet; then - git commit -m "Update Chart version to $CHART_VERSION" - else - echo "ℹ️ Chart.yaml already at version $CHART_VERSION" - fi - - git push origin release/$CHART_VERSION - else + # Normal flow publishes from main + START_POINT=origin/main + + # Refresh origin/main — it's both the branch point (START_POINT) + # and the fast-forward target at the end of this step. git fetch origin main - git checkout main # Fail fast if release branch already exists — indicates a partial release if git ls-remote --exit-code origin refs/heads/release/$CHART_VERSION &>/dev/null; then @@ -172,32 +150,37 @@ jobs: echo "Use force_republish=true to redo it from scratch" exit 1 fi + fi - # Update services versions if provided - if [ "$SERVICES_VERSION" != "" ]; then - ./lock_versions $SERVICES_VERSION - git add . - if ! git diff --staged --quiet; then - git commit -m "Update Braintrust Services versions to $SERVICES_VERSION" - else - echo "No changes to commit for services version update" - fi - fi + # Create the release branch from the chosen starting point + git checkout -b release/$CHART_VERSION $START_POINT - # Update Chart version on main - sed -i "s/^version: .*/version: $CHART_VERSION/" $CHART_PATH/Chart.yaml - git add $CHART_PATH/Chart.yaml + # Apply services version lock if provided + if [ "$SERVICES_VERSION" != "" ]; then + ./lock_versions $SERVICES_VERSION + git add . if ! git diff --staged --quiet; then - git commit -m "Update Chart version to $CHART_VERSION" + git commit -m "Update Braintrust Services versions to $SERVICES_VERSION" else - echo "No changes to commit for Chart version update" + echo "No changes to commit for services version update" fi + fi - git push origin main + # Update Chart version + sed -i "s/^version: .*/version: $CHART_VERSION/" $CHART_PATH/Chart.yaml + git add $CHART_PATH/Chart.yaml + if ! git diff --staged --quiet; then + git commit -m "Update Chart version to $CHART_VERSION" + else + echo "ℹ️ Chart.yaml already at version $CHART_VERSION" + fi - # Create release branch from main's updated HEAD - git checkout -b release/$CHART_VERSION - git push origin release/$CHART_VERSION + git push origin release/$CHART_VERSION + + # In normal mode, fast-forward main to include the version bumps. + # (Skipped for force_republish: the release branch diverges from main.) + if [[ "${{ github.event.inputs.force_republish }}" != "true" ]]; then + git push origin release/$CHART_VERSION:main fi env: GH_TOKEN: ${{ steps.bot-token.outputs.token }}