fix(workflows): delete and recreate immutable release as draft#550
fix(workflows): delete and recreate immutable release as draft#550WilliamBerryiii merged 1 commit intomainfrom
Conversation
- published releases are immutable on this repo (HTTP 422) - replace gh release edit --draft=true with delete/recreate cycle - capture release metadata, delete immutable release, recreate as draft - tag persists across the delete/create cycle for downstream jobs 🔧 - Generated by Copilot
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
There was a problem hiding this comment.
Pull request overview
Updates the main release workflow to handle GitHub’s “immutable published releases” behavior by replacing the prior “edit to draft” approach with a delete-and-recreate flow that results in a mutable draft release for asset uploads.
Changes:
- Capture the just-created release’s title/body, delete the immutable published release, and recreate it as a draft tied to the existing tag.
- Keep downstream jobs uploading assets against the same
tag_name, then publish the draft at the end of the workflow.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #550 +/- ##
==========================================
- Coverage 85.36% 85.34% -0.03%
==========================================
Files 23 23
Lines 4475 4475
==========================================
- Hits 3820 3819 -1
- Misses 655 656 +1
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
WilliamBerryiii
left a comment
There was a problem hiding this comment.
Dismissing the automated review comments — none apply here:
-
Null fields: release-please always sets
nameandbodywhen creating a release. This step gates onrelease_created == 'true', so both fields are guaranteed populated. -
Redundant API calls: Two
gh release viewcalls in a release pipeline that runs occasionally is negligible overhead. The suggestion introduces ajqdependency whengh --json -qalready handles filtering natively. -
Retry/rollback logic: If
gh release createfails, the step fails visibly in CI — re-run the workflow to recover. The suggested rollback recreates the release as published (immutable), which puts us right back where we started. Adding 40+ lines of retry/rollback for a near-impossible transient failure is over-engineering.
🤖 I have created a release *beep* *boop* --- ## [2.3.6](hve-core-v2.3.5...hve-core-v2.3.6) (2026-02-13) ### 🐛 Bug Fixes * **workflows:** delete and recreate immutable release as draft ([#550](#550)) ([75217da](75217da)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: hve-core-release-please[bot] <254602402+hve-core-release-please[bot]@users.noreply.github.com>
## Description The `publish-release` job at the end of the release pipeline uses `gh release edit --draft=false` to publish the draft release after asset uploads complete. This fails with `HTTP 422: tag_name was used by an immutable release` — GitHub enforces immutability at the tag level, so any release using a tag that was previously associated with an immutable release cannot be edited via `gh release edit`. This applies the same delete-and-recreate pattern from PR #550 to the `publish-release` job: capture the draft's metadata, delete it, and recreate as a published release. Also consolidates both recreate steps (draft and publish) to use a single `gh release view --json name,body` API call with `jq` instead of two separate queries. ## Related Issue(s) Fixes #543 ## Type of Change Select all that apply: **Code & Documentation:** - [x] Bug fix (non-breaking change fixing an issue) - [ ] New feature (non-breaking change adding functionality) - [ ] Breaking change (fix or feature causing existing functionality to change) - [ ] Documentation update **Infrastructure & Configuration:** - [x] GitHub Actions workflow - [ ] Linting configuration (markdown, PowerShell, etc.) - [ ] Security configuration - [ ] DevContainer configuration - [ ] Dependency update **Other:** - [ ] Script/automation (`.ps1`, `.sh`, `.py`) - [ ] Other (please describe): ## Testing - v2.3.6 release confirmed that asset uploads succeeded with the draft recreate from PR #550 - The only failure was the `publish-release` job's `gh release edit --draft=false` — this PR fixes that final step ## Checklist ### Required Checks - [x] Documentation is updated (if applicable) - [x] Files follow existing naming conventions - [x] Changes are backwards compatible (if applicable) - [ ] Tests added for new functionality (if applicable) ### Required Automated Checks The following validation commands must pass before merging: - [ ] Markdown linting: `npm run lint:md` - [ ] Spell checking: `npm run spell-check` - [ ] Frontmatter validation: `npm run lint:frontmatter` - [ ] Link validation: `npm run lint:md-links` - [ ] PowerShell analysis: `npm run lint:ps` ## Security Considerations - [x] This PR does not contain any sensitive or NDA information - [x] Any new dependencies have been reviewed for security issues - [x] Security-related scripts follow the principle of least privilege ## Additional Notes After this change, the full release lifecycle is: release-please creates published (immutable) release → delete and recreate as draft (mutable) → package/attest/upload assets → delete draft and recreate as published. No `gh release edit` calls remain in the pipeline.
…rors (#554) # Pull Request ## Description Fixes the persistent `HTTP 422: tag_name was used by an immutable release` error in the `publish-release` job. This is the **fifth iteration** (PRs #538 → #545 → #550 → #552) — all previous approaches failed because they attempted to delete and recreate releases or tags after publication, which GitHub's immutability model permanently forbids. ### Root Cause GitHub's [immutable releases documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases#about-immutable-releases) states: > *"Git tags cannot be moved or deleted"* for immutable releases. *"Even if you delete a repository and create a new one with the same name, you cannot reuse tags that were associated with immutable releases."* The tag name is **permanently tainted** at GitHub's infrastructure level once a release is published with immutability enabled. Every delete/recreate approach was fundamentally doomed: | PR | Approach | Failure Mode | |----|----------|-------------| | #538 | `"draft": true` in config | Race condition — release-please can't find draft releases (lazy tag) | | #545 | `gh release edit --draft=true` | Can't edit published immutable release | | #550 | Delete published, recreate as draft | Worked for upload, but publish step failed | | #552 | Delete draft, recreate as published | HTTP 422 — tag name permanently reserved | ### Solution: Draft-First Flow Follow [GitHub's recommended workflow](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases#creating-immutable-releases): 1. **release-please creates release as draft** (`"draft": true` in config) 2. **Explicit git tag creation** via API (workaround for release-please's lazy tag behavior) 3. **Upload assets to the mutable draft** (existing `attest-and-upload` and `upload-plugin-packages` jobs) 4. **Publish**: `gh release edit --draft=false` — release becomes immutable with all assets already attached ### Draft Race Condition Workaround release-please with `"draft": true` uses lazy tag creation — the git tag isn't materialized until publish. Without the tag, release-please's GraphQL query returns null for `tag` and `tagCommit`, causing it to skip the draft and propose a bogus version bump. This was fixed upstream in [googleapis/release-please#2627](googleapis/release-please#2627) (`force-tag-creation` option), but the fix is **not yet released** in release-please-action (latest: v4.4.0 → release-please 17.1.3). We work around this by explicitly creating the git tag via API after draft creation. When a new release-please-action ships with PR #2627, replace the manual tag creation step with `"force-tag-creation": true` in `release-please-config.json`. ## Related Issue(s) Supersedes PRs #538, #545, #550, #552 ## Type of Change Select all that apply: **Code & Documentation:** - [x] Bug fix (non-breaking change fixing an issue) **Infrastructure & Configuration:** - [x] GitHub Actions workflow ## Testing - Verified YAML and JSON syntax (no lint errors) - Logic validated against [GitHub REST API docs](https://docs.github.com/en/rest/releases/releases), [immutable releases concept](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases#about-immutable-releases), and [release-please issue #1650](googleapis/release-please#1650) - The draft-first approach avoids all immutability enforcement because assets are uploaded to mutable drafts, and publish is a single `--draft=false` edit (not a delete/create) ## Checklist ### Required Checks - [x] Documentation is updated (if applicable) - [x] Files follow existing naming conventions - [x] Changes are backwards compatible (if applicable) ## Security Considerations - [x] This PR does not contain any sensitive or NDA information - [x] Any new dependencies have been reviewed for security issues ## Additional Notes - **v2.3.7 remediation**: The v2.3.7 tag is permanently tainted. A manual release may be needed if v2.3.7 assets need to be published. The next release-please cycle (v2.3.8+) will use the draft-first flow and should succeed cleanly. - **Future upgrade**: Once `release-please-action` includes `force-tag-creation` support, the manual tag creation step can be replaced with a single config line. This is documented in a code comment referencing googleapis/release-please#2627.
Description
Published releases on this repository are immutable — the
gh release edit --draft=truestep introduced in PR #545 fails withHTTP 422: state cannot be changed when release is immutable. This PR replaces the edit approach with a delete-and-recreate cycle: capture the release metadata, delete the immutable published release, and recreate it as a mutable draft so downstream jobs can upload assets. The git tag persists across the delete/create cycle, and thepublish-releasejob converts the draft back to published after upload completes.Why prior approaches failed:
"draft": truein config) — release-please cannot find draft releases via the Releases API, causing it to scan full history and propose a bogus v3.0.0gh release edit --draft=true) — published releases are immutable on this repo (HTTP 422)Related Issue(s)
Fixes #543
Type of Change
Select all that apply:
Code & Documentation:
Infrastructure & Configuration:
AI Artifacts:
prompt-builderagent and addressed all feedback.github/instructions/*.instructions.md).github/prompts/*.prompt.md).github/agents/*.agent.md).github/skills/*/SKILL.md)Other:
.ps1,.sh,.py)Testing
main.ymlwithyamllintgh release deletepreserves the git tag (documented GitHub CLI behavior)gh release create --draft --verify-tagrecreates a mutable draft tied to the existing tag--notes-fileapproach safely handles release body content with special charactersChecklist
Required Checks
Required Automated Checks
The following validation commands must pass before merging:
npm run lint:mdnpm run spell-checknpm run lint:frontmatternpm run lint:md-linksnpm run lint:psSecurity Considerations
Additional Notes
The delete-and-recreate approach is safe because:
gh release deleteonly removes the release object; the git tag remains intact--notes-file(avoids shell interpolation issues)steps.release.outputs.tag_name, which is unaffected by the recreate