From c7b8e1a573040bf2169bdeb120573b1030ccdd9b Mon Sep 17 00:00:00 2001 From: farhan Date: Tue, 1 Sep 2026 19:46:49 +0500 Subject: [PATCH] fix: make release workflow compatible with immutable releases The org has immutable releases enabled, which freezes a release's assets the moment it is published. The old flow let python-semantic-release publish the GitHub Release and then attached the built distributions afterwards via publish-action, which now fails with HTTP 422 ("cannot upload assets to an immutable release"). That failure aborts the release job before publish_to_pypi runs, so the tag is created but never shipped to PyPI (v6.3.2 is tagged but missing from PyPI). Set vcs_release: "false" so python-semantic-release commits, tags, and builds but does not publish the release, then create it with `gh release create` which uploads the assets to a draft and publishes it atomically -- the ordering immutable releases require. All actions are SHA-pinned to match the sample-plugin standard. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 42 ++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ed61ead6..4c1cb0570 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,40 +31,62 @@ jobs: - name: Python Semantic Release id: release - uses: python-semantic-release/python-semantic-release@v10.6.2 + uses: python-semantic-release/python-semantic-release@9a026e9303981c866c3425723009becb2437c757 # v10.6.2 with: github_token: ${{ secrets.OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "github-actions@github.com" changelog: "false" + # Commit, tag, push and build, but don't create the GitHub release. + # We create it ourselves in the next step so that the distributions + # are attached before the release is published. See that step for why. + vcs_release: "false" - - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.6.1 + # This repo has immutable releases enabled, which freezes a release's + # assets the moment it is published, so assets cannot be attached + # afterwards. `gh release create` handles this by creating the release as + # a draft, uploading the assets, and only then publishing it: + # https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases + - name: Publish | Create GitHub Release with Assets if: steps.release.outputs.released == 'true' - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ steps.release.outputs.tag }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Reuse the release notes python-semantic-release generated for us. + RELEASE_NOTES: ${{ steps.release.outputs.release_notes }} + TAG: ${{ steps.release.outputs.tag }} + run: | + # Output the release notes to a file + printf '%s' "$RELEASE_NOTES" > "$RUNNER_TEMP/release_notes.md" + # Creates the release as a draft, uploads the assets, and then + # publishes it -- all within this one command. + gh release create "$TAG" \ + --verify-tag \ + --title "$TAG" \ + --notes-file "$RUNNER_TEMP/release_notes.md" \ + dist/* - name: Upload dist artifacts if: steps.release.outputs.released == 'true' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: dist path: dist/ + if-no-files-found: error publish_to_pypi: runs-on: ubuntu-latest needs: release - if: needs.release.outputs.released == 'true' + if: github.ref_name == 'master' && needs.release.outputs.released == 'true' permissions: + contents: read id-token: write steps: - name: Download dist artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist path: dist/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2