Skip to content

fix(workflows): delete and recreate immutable release as draft#550

Merged
WilliamBerryiii merged 1 commit intomainfrom
fix/immutable-release-delete-recreate
Feb 13, 2026
Merged

fix(workflows): delete and recreate immutable release as draft#550
WilliamBerryiii merged 1 commit intomainfrom
fix/immutable-release-delete-recreate

Conversation

@WilliamBerryiii
Copy link
Member

Description

Published releases on this repository are immutable — the gh release edit --draft=true step introduced in PR #545 fails with HTTP 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 the publish-release job converts the draft back to published after upload completes.

Why prior approaches failed:

Related Issue(s)

Fixes #543

Type of Change

Select all that apply:

Code & Documentation:

  • 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:

  • GitHub Actions workflow
  • Linting configuration (markdown, PowerShell, etc.)
  • Security configuration
  • DevContainer configuration
  • Dependency update

AI Artifacts:

  • Reviewed contribution with prompt-builder agent and addressed all feedback
  • Copilot instructions (.github/instructions/*.instructions.md)
  • Copilot prompt (.github/prompts/*.prompt.md)
  • Copilot agent (.github/agents/*.agent.md)
  • Copilot skill (.github/skills/*/SKILL.md)

Other:

  • Script/automation (.ps1, .sh, .py)
  • Other (please describe):

Testing

  • Validated YAML syntax of main.yml with yamllint
  • Confirmed gh release delete preserves the git tag (documented GitHub CLI behavior)
  • Confirmed gh release create --draft --verify-tag recreates a mutable draft tied to the existing tag
  • Verified --notes-file approach safely handles release body content with special characters

Checklist

Required Checks

  • Documentation is updated (if applicable)
  • Files follow existing naming conventions
  • 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

  • This PR does not contain any sensitive or NDA information
  • Any new dependencies have been reviewed for security issues
  • Security-related scripts follow the principle of least privilege

Additional Notes

The delete-and-recreate approach is safe because:

  1. Tag preservationgh release delete only removes the release object; the git tag remains intact
  2. Timing — release-please has already completed version anchoring before this step runs, so the brief absence of the release object has no effect
  3. Metadata fidelity — release title and body are captured before deletion and reapplied to the new draft via --notes-file (avoids shell interpolation issues)
  4. Downstream compatibility — all subsequent jobs reference steps.release.outputs.tag_name, which is unaffected by the recreate

- 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
@WilliamBerryiii WilliamBerryiii requested a review from a team as a code owner February 13, 2026 23:08
Copilot AI review requested due to automatic review settings February 13, 2026 23:08
@github-actions
Copy link

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.34%. Comparing base (fe21499) to head (f551177).

Additional details and impacted files

Impacted file tree graph

@@            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     
Flag Coverage Δ
pester 85.34% <ø> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@WilliamBerryiii WilliamBerryiii merged commit 75217da into main Feb 13, 2026
24 checks passed
Copy link
Member Author

@WilliamBerryiii WilliamBerryiii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dismissing the automated review comments — none apply here:

  1. Null fields: release-please always sets name and body when creating a release. This step gates on release_created == 'true', so both fields are guaranteed populated.

  2. Redundant API calls: Two gh release view calls in a release pipeline that runs occasionally is negligible overhead. The suggestion introduces a jq dependency when gh --json -q already handles filtering natively.

  3. Retry/rollback logic: If gh release create fails, 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.

WilliamBerryiii pushed a commit that referenced this pull request Feb 13, 2026
🤖 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>
WilliamBerryiii added a commit that referenced this pull request Feb 13, 2026
## 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.
WilliamBerryiii added a commit that referenced this pull request Feb 14, 2026
…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.
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.

fix: release-please draft release race condition causes bogus v3.0.0 version bumps and extension publish workflows require manual version input

3 participants