Skip to content

build: attach release assets before publishing the release - #57

Merged
feanil merged 2 commits into
mainfrom
feanil/fix_immutable_release_assets
Aug 25, 2026
Merged

build: attach release assets before publishing the release#57
feanil merged 2 commits into
mainfrom
feanil/fix_immutable_release_assets

Conversation

@feanil

@feanil feanil commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What happened

The v3.7.1 release run failed at Publish | Upload to GitHub Release Assets:

HTTPError: 422 Client Error: Unprocessable Entity for url:
https://uploads.github.com/repos/openedx/sample-plugin/releases/376463625/assets?name=openedx_plugin_sample-3.7.1.tar.gz
Failed to upload asset 'dist/openedx_plugin_sample-3.7.1.tar.gz' to release (HTTP 422)

Root cause

The openedx org now has immutable releases enabled, which freezes a release's assets the moment the release is published. Our flow published the release first and uploaded the distributions in a separate step afterwards, which GitHub now rejects with Cannot upload assets to an immutable release.

The immutable flag on our releases lines up exactly with when assets stopped appearing:

tag immutable assets
v3.7.1 true 0
v3.7.0 true 0
v3.6.1 true 0
v3.6.0 false 2

So this has been broken since v3.6.1 (2026-07-15), but the job stayed green because python-semantic-release swallowed asset upload errors until v10.6.0 fixed that. Bumping to v10.6.1 in #56 didn't break anything — it surfaced a pre-existing failure. The patch_tags = [..., "build"] addition in that same PR is why the path got exercised immediately, which is exactly what it was added for.

The fix

gh release create creates the release as a draft, uploads the assets, and only then publishes it — the sequence immutable releases require. So:

  • vcs_release: "false" on the python-semantic-release step, so it still tags, pushes and builds, but doesn't create the release
  • publish-action is replaced by a gh release create step that passes the dists as arguments
  • PSR's generated notes are reused via the release_notes output, so the release body is unchanged

Second commit drops OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN in favour of the default GITHUB_TOKEN. That PAT was added in bed319c so PSR could push the changelog commit past branch protection on main; 53c8f3f then turned the changelog off, so PSR only pushes a tag now (No local changes to add to any commit) and never writes to the protected branch. Tag and release creation are both covered by the contents: write permission the job already grants, and the repo has no tag protection rules or rulesets to bypass.

Verified

  • Draft-first has been gh release create's behaviour since at least v2.20.0, so this doesn't depend on a recent gh on the runner
  • released and release_notes are both set before the make_vcs_release branch in PSR's version.py, so they survive vcs_release: "false"
  • Release notes containing backticks, $(...) or quotes pass through unexpanded (env var + printf '%s' + --notes-file)

The end-to-end behaviour can only be tested by publishing a real release. Since this is a build: commit, merging it releases 3.7.2 and validates itself.

Follow-ups, not in this PR

  • 3.7.1 never reached PyPI or npm. The failing step aborted the rest of the job, so the artifact upload and all three publish jobs were skipped. Re-running won't help — the tag exists, so PSR would report released=false. Letting 3.7.2 carry it is the plan; PyPI would skip 3.7.1.
  • v3.6.1, v3.7.0 and v3.7.1 are permanently asset-less. They're immutable, so they can't be backfilled.
  • An upstream issue for python-semantic-release is drafted; there's no existing report for this.

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.04%. Comparing base (43d749c) to head (2bb4364).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #57   +/-   ##
=======================================
  Coverage   93.04%   93.04%           
=======================================
  Files          16       16           
  Lines         503      503           
  Branches       18       18           
=======================================
  Hits          468      468           
  Misses         24       24           
  Partials       11       11           
Flag Coverage Δ
unittests 93.04% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

feanil and others added 2 commits August 25, 2026 11:05
The openedx org now has immutable releases enabled, which freezes a
release's assets at the moment the release is published. Our flow
created and published the release with python-semantic-release and then
uploaded the distributions in a separate step, so those uploads now fail
with "HTTP 422: Cannot upload assets to an immutable release".

Instead, let python-semantic-release tag, push and build
(vcs_release: false) and create the release ourselves with
`gh release create`, which creates the release as a draft, uploads the
assets, and only then publishes it. Its generated release notes are
reused via the release_notes output, so the release body is unchanged.

This has been broken since v3.6.1 but stayed silent until now, because
python-semantic-release swallowed asset upload errors before v10.6.0.
v3.6.1, v3.7.0 and v3.7.1 all published with no release assets, and
they are immutable now, so they cannot be backfilled.

Immutable releases: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases
Silent upload failures: python-semantic-release/python-semantic-release#1395
Failing run: https://github.com/openedx/sample-plugin/actions/runs/32858608339/job/97836889093

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN was introduced in bed319c so that
python-semantic-release could push the changelog commit past the branch
protection rules on main. 53c8f3f then turned the changelog off, so
python-semantic-release only pushes a tag now ("No local changes to add
to any commit") and never writes to the protected branch.

Tag creation and release creation are both covered by the
`contents: write` permission this job already grants, and the repo has
no tag protection rules or rulesets that the default token would need
to bypass. That drops our reliance on a shared org-level PAT.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@feanil
feanil force-pushed the feanil/fix_immutable_release_assets branch from 012edf6 to 2bb4364 Compare August 25, 2026 15:06
@feanil
feanil marked this pull request as ready for review August 25, 2026 15:08
@feanil
feanil merged commit 5d5da4e into main Aug 25, 2026
9 checks passed
@feanil
feanil deleted the feanil/fix_immutable_release_assets branch August 25, 2026 15:08
irfanuddinahmad added a commit to irfanuddinahmad/ccx-keys that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/openedx-filters that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to openedx/openedx-ledger that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to openedx/edx-enterprise that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/openedx-chem that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/auth-backends that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/openedx-authz that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/xss-utils that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/event-routing-backends that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/event-bus-kafka that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/event-bus-redis that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to openedx/edx-enterprise-subsidy-client that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to openedx/edx-enterprise-data that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to openedx/enterprise-integrated-channels that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to openedx/taxonomy-connector that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it would 422 on the first real release. Matches the fix already proven and merged on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
bmtcril pushed a commit to openedx/event-tracking that referenced this pull request Aug 27, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. The old flow (main PSR step publishes the release, a separate publish-action step attaches assets afterward) can never work under that constraint -- it 422s every time, which is what happened on v4.1.0 (issue #433). Matches the fix already proven and merged on openedx/sample-plugin#57: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call.
irfanuddinahmad added a commit to irfanuddinahmad/opaque-keys that referenced this pull request Aug 28, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. Previously this workflow didn't even attempt to attach dist files to the GitHub release (relying only on the actions/upload-artifact -> download-artifact path for PyPI publish), so every release shipped with an empty GitHub Release page. Matches the fix already proven on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call. Not adding the separate gitpython/uv workaround from event-tracking#435/openedx#436 here -- these PRs won't merge until upstream python-semantic-release fixes the GitPython 3.1.60 break (python-semantic-release/python-semantic-release#1476).
irfanuddinahmad added a commit to irfanuddinahmad/openedx-core that referenced this pull request Aug 28, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. Previously this workflow didn't even attempt to attach dist files to the GitHub release (relying only on the actions/upload-artifact -> download-artifact path for PyPI publish), so every release shipped with an empty GitHub Release page. Matches the fix already proven on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call. Not adding the separate gitpython/uv workaround from event-tracking#435/openedx#436 here -- these PRs won't merge until upstream python-semantic-release fixes the GitPython 3.1.60 break (python-semantic-release/python-semantic-release#1476).
irfanuddinahmad added a commit to irfanuddinahmad/openedx-calc that referenced this pull request Aug 28, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. Previously this workflow didn't even attempt to attach dist files to the GitHub release (relying only on the actions/upload-artifact -> download-artifact path for PyPI publish), so every release shipped with an empty GitHub Release page. Matches the fix already proven on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call. Not adding the separate gitpython/uv workaround from event-tracking#435/#436 here -- these PRs won't merge until upstream python-semantic-release fixes the GitPython 3.1.60 break (python-semantic-release/python-semantic-release#1476).
irfanuddinahmad added a commit to irfanuddinahmad/edx-rest-api-client that referenced this pull request Aug 28, 2026
This repo has immutable releases enabled, which freezes a release's assets the moment it's published. Previously this workflow didn't even attempt to attach dist files to the GitHub release (relying only on the actions/upload-artifact -> download-artifact path for PyPI publish), so every release shipped with an empty GitHub Release page. Matches the fix already proven on openedx/sample-plugin#57 and validated end-to-end on openedx/event-tracking#434: build without publishing (vcs_release: false), then create the release with dist/* attached in one gh release create call. Not adding the separate gitpython/uv workaround from event-tracking#435/openedx#436 here -- these PRs won't merge until upstream python-semantic-release fixes the GitPython 3.1.60 break (python-semantic-release/python-semantic-release#1476).
farhan added a commit to openedx/xblocks-core that referenced this pull request Sep 1, 2026
Attach release assets via a draft `gh release create` instead of
python-semantic-release/publish-action, which fails with HTTP 422 once
immutable releases freeze a published release's assets. Set
vcs_release: "false" so semantic-release builds and tags but leaves
GitHub Release creation to us, guaranteeing dists attach before publish.
Also bump python-semantic-release to v10.6.2. Mirrors openedx/sample-plugin#57.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant