From 175a2ff423f0f76ea7eb899844b4f52c7ae9f5d3 Mon Sep 17 00:00:00 2001 From: Thomas Jung <12159356+jung-thomas@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:09:49 +0200 Subject: [PATCH] ci: make release workflows respect branch protection (dispatch opens PR) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both release workflows' workflow_dispatch path did `git push origin HEAD:main` to land the version-bump commit, which the branch-protection rulesets reject (GH013). This broke one-click releases — the v4.202607.0 release had to be completed manually (bump via PR + tag push). Rework the release flow into two branch-protection-safe phases: 1. workflow_dispatch -> `prepare` now calculates the version (via scripts/prepare-release.js, unchanged), commits to a `release/v` branch, and opens a PR into main via `gh pr create` (no push to main). 2. Merging that PR (push to main) -> new `auto-tag` job reads package.json's version and, if untagged, creates+pushes the version tag. Tags are not branch-protected, so this succeeds and triggers the existing tag path. 3. Tag push -> resolve-version -> publish -> (mcp registry) — unchanged, and already verified working during the v4.202607.0 release. Details: - publish / publish-mcp-registry decoupled from `prepare` (now depend only on resolve-version); their Determine-version steps read resolve-version outputs unconditionally since publish only runs on the tag path now. - Job guards added so a plain main push only runs auto-tag: test and resolve-version are gated on the version-tag ref (`v*` / `agent-v*`) or dispatch; auto-tag is gated on the main-branch push. - auto-tag is idempotent (no-op when the version tag already exists), so the shared `push: branches: [main]` trigger is harmless across both workflows — each tags only when its own package version changes. - Added `pull-requests: write` permission (for `gh pr create`). - Removed the now-dead `publish_mcp_registry` dispatch input (dispatch no longer publishes directly). Applied identically to release-agent-instructions.yml (agent-v* prefix, agent-instructions/package.json). --- .../workflows/release-agent-instructions.yml | 83 ++++++++++---- .github/workflows/release-hana-cli.yml | 104 +++++++++++------- 2 files changed, 124 insertions(+), 63 deletions(-) diff --git a/.github/workflows/release-agent-instructions.yml b/.github/workflows/release-agent-instructions.yml index a74f1ae..909bf6c 100644 --- a/.github/workflows/release-agent-instructions.yml +++ b/.github/workflows/release-agent-instructions.yml @@ -4,6 +4,8 @@ on: push: tags: - 'agent-v*' + branches: + - main workflow_dispatch: inputs: version: @@ -13,6 +15,7 @@ on: permissions: contents: write + pull-requests: write concurrency: group: release-agent-instructions @@ -21,6 +24,9 @@ concurrency: jobs: test: name: Test Suite + # Release paths only: an agent-v* tag push or a manual dispatch. + # A plain push to main (merged release PR) is handled by auto-tag. + if: startsWith(github.ref, 'refs/tags/agent-v') || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -42,13 +48,10 @@ jobs: run: npm run test:ci prepare: - name: Prepare Release + name: Prepare Release PR needs: test if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest - outputs: - version: ${{ steps.calc.outputs.version }} - tag: ${{ steps.calc.outputs.tag }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -82,22 +85,62 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - name: Commit version bump + - name: Create release branch, commit, and open PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + VERSION="${{ steps.calc.outputs.version }}" + BRANCH="release/agent-v${VERSION}" git add agent-instructions/package.json - git diff --cached --quiet && echo "No changes to commit" && exit 0 - git commit -m "chore: release agent-instructions v${{ steps.calc.outputs.version }} [skip ci]" + if git diff --cached --quiet; then + echo "::error::No version bump changes to commit (version may already be $VERSION)" + exit 1 + fi + git checkout -b "$BRANCH" + git commit -m "chore: release agent-instructions v${VERSION}" + git push origin "$BRANCH" + gh pr create \ + --base main \ + --head "$BRANCH" \ + --title "chore: release agent-instructions v${VERSION}" \ + --body "Automated release version bump for **agent-instructions v${VERSION}**, opened by the Release workflow (workflow_dispatch). + + Merging this PR pushes to \`main\`, which triggers the \`auto-tag\` job to create tag \`agent-v${VERSION}\`, which runs the tag-triggered publish path (npm + GitHub release, gated by the \`npm-publish\` environment approval). + + Review the version bump, then merge to release." + + auto-tag: + name: Auto-tag merged release + # When a release PR is merged (push to main), tag the new agent-instructions + # version. Pushing the tag triggers this workflow via the 'agent-v*' path. + # Idempotent: only tags when the version has no tag yet, so ordinary pushes + # to main (and hana-cli release merges) are a no-op here. + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - - name: Create and push tag + - name: Tag new version if not already tagged run: | - git tag -a "agent-v${{ steps.calc.outputs.version }}" -m "Release agent-instructions v${{ steps.calc.outputs.version }}" - git push origin HEAD:${{ github.ref_name }} - git push origin "agent-v${{ steps.calc.outputs.version }}" + VERSION=$(jq -r '.version' agent-instructions/package.json) + if git rev-parse "agent-v${VERSION}" >/dev/null 2>&1; then + echo "Tag agent-v${VERSION} already exists — nothing to release." + exit 0 + fi + echo "Tagging new release agent-v${VERSION}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "agent-v${VERSION}" -m "Release agent-instructions v${VERSION}" + git push origin "agent-v${VERSION}" resolve-version: name: Resolve Version from Tag needs: test - if: github.event_name == 'push' + if: startsWith(github.ref, 'refs/tags/agent-v') runs-on: ubuntu-latest outputs: version: ${{ steps.extract.outputs.version }} @@ -113,11 +156,8 @@ jobs: publish: name: Publish to npm - needs: [test, prepare, resolve-version] - if: | - always() && - needs.test.result == 'success' && - (needs.prepare.result == 'success' || needs.resolve-version.result == 'success') + needs: [test, resolve-version] + if: needs.test.result == 'success' && needs.resolve-version.result == 'success' runs-on: ubuntu-latest environment: name: npm-publish @@ -126,13 +166,8 @@ jobs: - name: Determine version id: ver run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "version=${{ needs.prepare.outputs.version }}" >> $GITHUB_OUTPUT - echo "tag=${{ needs.prepare.outputs.tag }}" >> $GITHUB_OUTPUT - else - echo "version=${{ needs.resolve-version.outputs.version }}" >> $GITHUB_OUTPUT - echo "tag=${{ needs.resolve-version.outputs.tag }}" >> $GITHUB_OUTPUT - fi + echo "version=${{ needs.resolve-version.outputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ needs.resolve-version.outputs.tag }}" >> $GITHUB_OUTPUT - name: Checkout code at tag uses: actions/checkout@v4 diff --git a/.github/workflows/release-hana-cli.yml b/.github/workflows/release-hana-cli.yml index f71b536..6d5e982 100644 --- a/.github/workflows/release-hana-cli.yml +++ b/.github/workflows/release-hana-cli.yml @@ -4,6 +4,8 @@ on: push: tags: - 'v*' + branches: + - main workflow_dispatch: inputs: version: @@ -15,15 +17,11 @@ on: required: false type: string default: '[]' - publish_mcp_registry: - description: 'Also publish to MCP Registry?' - required: false - type: boolean - default: true permissions: contents: write id-token: write + pull-requests: write concurrency: group: release-hana-cli @@ -32,6 +30,9 @@ concurrency: jobs: test: name: Test Suite + # Run for the release paths only: a version tag push, or a manual dispatch. + # A plain push to main (e.g. a merged release PR) is handled by auto-tag. + if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -60,13 +61,10 @@ jobs: run: npm run test:ci prepare: - name: Prepare Release + name: Prepare Release PR needs: test if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest - outputs: - version: ${{ steps.calc.outputs.version }} - tag: ${{ steps.calc.outputs.tag }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -101,22 +99,65 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - name: Commit version bump and changelog + - name: Create release branch, commit, and open PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + VERSION="${{ steps.calc.outputs.version }}" + BRANCH="release/v${VERSION}" git add package.json npm-shrinkwrap.json server.json mcp-server/package.json mcp-server/npm-shrinkwrap.json CHANGELOG.json CHANGELOG.md - git diff --cached --quiet && echo "No changes to commit" && exit 0 - git commit -m "chore: release v${{ steps.calc.outputs.version }} [skip ci]" + if git diff --cached --quiet; then + echo "::error::No version bump changes to commit (version may already be $VERSION)" + exit 1 + fi + git checkout -b "$BRANCH" + git commit -m "chore: release v${VERSION}" + # Push the release branch (allowed — branch protection only blocks main). + git push origin "$BRANCH" + # Open a PR into main. Merging it triggers auto-tag -> publish. + gh pr create \ + --base main \ + --head "$BRANCH" \ + --title "chore: release v${VERSION}" \ + --body "Automated release version bump for **v${VERSION}**, opened by the Release workflow (workflow_dispatch). + + Merging this PR pushes to \`main\`, which triggers the \`auto-tag\` job to create tag \`v${VERSION}\`, which in turn runs the tag-triggered publish path (npm + GitHub release + MCP registry, gated by the \`npm-publish\` environment approval). - - name: Create and push tag + Review the version bump and changelog, then merge to release." + + + auto-tag: + name: Auto-tag merged release + # When a release PR is merged (push to main), tag the new version. Pushing + # the tag triggers this same workflow via the 'v*' tag path -> publish. + # Idempotent: only tags when package.json's version has no tag yet, so + # ordinary pushes to main are a no-op. + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Tag new version if not already tagged run: | - git tag -a "v${{ steps.calc.outputs.version }}" -m "Release v${{ steps.calc.outputs.version }}" - git push origin HEAD:${{ github.ref_name }} - git push origin "v${{ steps.calc.outputs.version }}" + VERSION=$(jq -r '.version' package.json) + if git rev-parse "v${VERSION}" >/dev/null 2>&1; then + echo "Tag v${VERSION} already exists — nothing to release." + exit 0 + fi + echo "Tagging new release v${VERSION}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "v${VERSION}" -m "Release v${VERSION}" + git push origin "v${VERSION}" resolve-version: name: Resolve Version from Tag needs: test - if: github.event_name == 'push' + if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest outputs: version: ${{ steps.extract.outputs.version }} @@ -132,11 +173,8 @@ jobs: publish: name: Publish to npm - needs: [test, prepare, resolve-version] - if: | - always() && - needs.test.result == 'success' && - (needs.prepare.result == 'success' || needs.resolve-version.result == 'success') + needs: [test, resolve-version] + if: needs.test.result == 'success' && needs.resolve-version.result == 'success' runs-on: ubuntu-latest environment: name: npm-publish @@ -145,13 +183,8 @@ jobs: - name: Determine version id: ver run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "version=${{ needs.prepare.outputs.version }}" >> $GITHUB_OUTPUT - echo "tag=${{ needs.prepare.outputs.tag }}" >> $GITHUB_OUTPUT - else - echo "version=${{ needs.resolve-version.outputs.version }}" >> $GITHUB_OUTPUT - echo "tag=${{ needs.resolve-version.outputs.tag }}" >> $GITHUB_OUTPUT - fi + echo "version=${{ needs.resolve-version.outputs.version }}" >> $GITHUB_OUTPUT + echo "tag=${{ needs.resolve-version.outputs.tag }}" >> $GITHUB_OUTPUT - name: Checkout code at tag uses: actions/checkout@v4 @@ -213,11 +246,8 @@ jobs: publish-mcp-registry: name: Publish to MCP Registry - needs: [publish, prepare, resolve-version] - if: | - always() && - needs.publish.result == 'success' && - (github.event_name == 'push' || inputs.publish_mcp_registry == true) + needs: [publish, resolve-version] + if: needs.publish.result == 'success' runs-on: ubuntu-latest permissions: id-token: write @@ -226,11 +256,7 @@ jobs: - name: Determine version id: ver run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "version=${{ needs.prepare.outputs.version }}" >> $GITHUB_OUTPUT - else - echo "version=${{ needs.resolve-version.outputs.version }}" >> $GITHUB_OUTPUT - fi + echo "version=${{ needs.resolve-version.outputs.version }}" >> $GITHUB_OUTPUT - name: Checkout code at tag uses: actions/checkout@v4