Skip to content

fix(workflows): delete and recreate draft release to publish#552

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

fix(workflows): delete and recreate draft release to publish#552
WilliamBerryiii merged 1 commit intomainfrom
fix/publish-release-immutable

Conversation

@WilliamBerryiii
Copy link
Member

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:

  • 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

Other:

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

Testing

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

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.

- publish-release job hit same immutability wall (HTTP 422)
- tag previously used by immutable release blocks gh release edit
- replace gh release edit --draft=false with delete/recreate cycle
- consolidate both steps to single API call with jq

🔧 - Generated by Copilot
@WilliamBerryiii WilliamBerryiii requested a review from a team as a code owner February 13, 2026 23:34
Copilot AI review requested due to automatic review settings February 13, 2026 23:34
@github-actions
Copy link

Dependency Review

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

Scanned Files

None

@WilliamBerryiii WilliamBerryiii merged commit e3d6fca into main Feb 13, 2026
22 checks passed
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 release pipeline in .github/workflows/main.yml to avoid gh release edit failures caused by GitHub release immutability tied to a tag, by switching the final “publish” step to a delete-and-recreate approach.

Changes:

  • Replace gh release edit --draft=false in the publish-release job with a delete-and-recreate flow.
  • Consolidate release metadata capture to a single gh release view --json name,body call parsed via jq.

Comment on lines +219 to +226
# Capture draft release metadata
RELEASE_JSON=$(gh release view "$TAG" --json name,body -R "$REPO")
NAME=$(echo "$RELEASE_JSON" | jq -r '.name')
echo "$RELEASE_JSON" | jq -r '.body' > /tmp/release-body.md
# Delete mutable draft; recreate as published
gh release delete "$TAG" --yes -R "$REPO"
gh release create "$TAG" --verify-tag \
--title "$NAME" --notes-file /tmp/release-body.md -R "$REPO"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Deleting the draft release here will also delete all release assets that were uploaded in the earlier attest-and-upload and upload-plugin-packages jobs. The subsequent gh release create recreates the release metadata but does not restore those assets, so the published release will end up missing binaries. Consider either (a) re-uploading the previously built artifacts after recreating the release (e.g., download workflow artifacts in this job and gh release upload them), or (b) using an API approach that can publish without deleting the release object (if GitHub allows it for your immutability mode).

Suggested change
# Capture draft release metadata
RELEASE_JSON=$(gh release view "$TAG" --json name,body -R "$REPO")
NAME=$(echo "$RELEASE_JSON" | jq -r '.name')
echo "$RELEASE_JSON" | jq -r '.body' > /tmp/release-body.md
# Delete mutable draft; recreate as published
gh release delete "$TAG" --yes -R "$REPO"
gh release create "$TAG" --verify-tag \
--title "$NAME" --notes-file /tmp/release-body.md -R "$REPO"
# Publish existing draft release without deleting assets
gh release edit "$TAG" --draft=false -R "$REPO"

Copilot uses AI. Check for mistakes.
@codecov-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.34%. Comparing base (c30e21a) to head (dc97a77).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #552      +/-   ##
==========================================
- 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 pushed a commit that referenced this pull request Feb 13, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.3.7](hve-core-v2.3.6...hve-core-v2.3.7)
(2026-02-13)


### 🐛 Bug Fixes

* **workflows:** delete and recreate draft release to publish
([#552](#552))
([e3d6fca](e3d6fca))

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