Skip to content

fix(workflows): replace draft release config with post-creation draft conversion#545

Merged
WilliamBerryiii merged 1 commit intomainfrom
fix/543-release-draft-race-condition
Feb 13, 2026
Merged

fix(workflows): replace draft release config with post-creation draft conversion#545
WilliamBerryiii merged 1 commit intomainfrom
fix/543-release-draft-race-condition

Conversation

@WilliamBerryiii
Copy link
Member

@WilliamBerryiii WilliamBerryiii commented Feb 13, 2026

Description

Fixes a race condition where release-please "draft": true configuration caused draft releases that are excluded from the GitHub Releases API "latest" query, making them invisible to release-please's own version anchoring within the same workflow invocation. Because the draft release is invisible, release-please scans the full commit history, finds an old breaking change (PR #277), and proposes erroneous v3.0.0 major version bumps. Also added auto-detection of version from the latest GitHub release tag in both extension publish workflows, removing the requirement for manual version input on every dispatch.

Proof the race condition fires in production: after v2.3.4 merged, release-please created the v2.3.4 release as draft at 22:05:23 UTC. At 22:08:14 UTC — while v2.3.4 was still draft — release-please opened PR #547 proposing v3.0.0. The v2.3.4 release was not published until 22:10:08 UTC, confirming that release-please computed the next version while v2.3.4 was invisible to its own Releases API query.

Additionally, v2.3.2 remains stuck in draft state, demonstrating that the publish-release job's gh release edit --draft=false is unreliable when it depends on upstream jobs that may not always run.

Changes

  • fix(workflows): removed "draft": true from root and package level in release-please-config.json so release-please creates published releases that are immediately visible to the Releases API for version anchoring
  • fix(workflows): removed "force-tag-creation": true from release-please-config.json since it requires release-please v17.2.0+ and was silently ignored by the bundled v17.1.3
  • fix(workflows): replaced 20-line tag bridge step in main.yml with a gh release edit --draft=true post-creation conversion, allowing assets to be uploaded to a mutable draft release while preserving release visibility for version anchoring
  • feat(workflows): added auto-detection of version from latest GitHub release tag in extension-publish-prerelease.yml when version input is empty, with ODD minor derivation for the pre-release channel
  • feat(workflows): added auto-detection of version from latest GitHub release tag in extension-publish.yml when version input is empty, with hve-core-v prefix stripping
  • fix(workflows): updated extension-publish.yml input description to match new auto-detect behavior (was incorrectly referencing package.json)

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 linting passes via npm run lint:yaml — all 25 workflow files passed
  • Verified release-please-config.json is valid JSON with no schema errors
  • Confirmed the publish-release job already contains gh release edit --draft=false to finalize releases after asset upload
  • Confirmed gh release view --json tagName correctly returns the latest release tag format (hve-core-v2.3.4)

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 release lifecycle after this change follows a four-phase sequence: release-please creates a published release (tag created, visible to Releases API) → post-creation step converts to draft (mutable for asset upload) → package/attest/upload jobs attach assets → publish-release job converts back to published. This preserves the original HTTP 422 fix from PR #538 while eliminating the version anchoring race condition that caused PRs #530, #532, #534, #539, #540, #542, and #547.

Why the tag bridge didn't work

The tag bridge step from PR #538 manually created git tags after draft release creation. However, release-please anchors its version calculation on the GitHub Releases API (not the Tags API). Even though tag hve-core-v2.3.4 existed at commit 9a72f2b, release-please's query for the latest release excluded the draft, causing it to scan the full commit history and hit the old breaking change from PR #277.

@WilliamBerryiii WilliamBerryiii requested a review from a team as a code owner February 13, 2026 21:58
Copilot AI review requested due to automatic review settings February 13, 2026 21:58
@github-actions
Copy link

github-actions bot commented Feb 13, 2026

Dependency Review

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

Scanned Files

None

@codecov-commenter
Copy link

codecov-commenter commented Feb 13, 2026

Codecov Report

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

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #545      +/-   ##
==========================================
- 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.

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

This PR updates the release automation workflows to eliminate a release-please version anchoring race caused by draft releases not creating tags, and improves extension publish workflows by auto-deriving the version from the latest GitHub release when not provided.

Changes:

  • Removed draft: true and force-tag-creation from release-please-config.json so release-please creates a tag immediately for version anchoring.
  • Simplified the “tag bridge” logic in main.yml by converting the created release to a draft post-creation using gh release edit --draft=true.
  • Made extension publish workflows auto-detect the version from the latest GitHub release tag when the version input is empty (with ODD-minor derivation for the prerelease workflow).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
release-please-config.json Removes draft/force-tag-creation settings so release-please creates visible tags for anchoring.
.github/workflows/main.yml Replaces manual tag creation bridge with post-creation conversion of the release to draft for asset upload.
.github/workflows/extension-publish.yml Adds version auto-detection from latest GitHub release tag when workflow input is empty.
.github/workflows/extension-publish-prerelease.yml Makes version optional and auto-derives an ODD-minor prerelease version from the latest GitHub release.

… conversion

- remove draft:true from release-please-config.json to fix tag visibility race condition
- replace tag bridge step with gh release edit --draft=true post-creation conversion
- add auto-detect version from latest release tag in extension publish workflows
- derive ODD minor version for pre-release channel automatically

Fixes #543
🔧 - Generated by Copilot
@WilliamBerryiii WilliamBerryiii force-pushed the fix/543-release-draft-race-condition branch from 3d932be to 8590251 Compare February 13, 2026 22:30
@WilliamBerryiii WilliamBerryiii merged commit 2311d04 into main Feb 13, 2026
18 checks passed
WilliamBerryiii pushed a commit that referenced this pull request Feb 13, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.3.5](hve-core-v2.3.4...hve-core-v2.3.5)
(2026-02-13)


### 🐛 Bug Fixes

* **workflows:** replace draft release config with post-creation draft
conversion ([#545](#545))
([2311d04](2311d04))

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

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

- PR #538 (`"draft": true` in config) — release-please cannot find draft
releases via the Releases API, causing it to scan full history and
propose a bogus v3.0.0
- PR #545 (`gh 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:**

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

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

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

The delete-and-recreate approach is safe because:

1. **Tag preservation** — `gh 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
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