Skip to content

fix(publish): use gh api directly to create release with explicit tag_name#508

Merged
jdx merged 1 commit intomainfrom
chore/use-communique
Feb 18, 2026
Merged

fix(publish): use gh api directly to create release with explicit tag_name#508
jdx merged 1 commit intomainfrom
chore/use-communique

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Feb 18, 2026

Summary

  • Previous fix (fix(publish): anchor gh release create to GITHUB_SHA to avoid untagged release #506) added --target "$GITHUB_SHA" to gh release create, but the releases are still created as untagged-*
  • Root cause (revised): something creates a GitHub release for the tag ~60 seconds before the publish-cli workflow starts (timestamps: v2.17.1 release at 12:36:44, workflow start at 12:37:44; v2.17.2 release at 13:07:56, workflow start at 13:08:47). When gh release create "v2.17.x" finds an existing release for that tag, it silently creates a new untagged-* draft as a fallback instead of failing
  • Fix: call gh api repos/${{ github.repository }}/releases --method POST directly with explicit -f tag_name= and -f target_commitish= fields. This bypasses the gh release create wrapper entirely and guarantees the tag_name is set correctly regardless of pre-existing releases

Test plan

  • Verify next release creates a draft with tag_name: v2.x.x (not untagged-*) via gh api repos/jdx/usage/releases --jq '.[0]'
  • Confirm taiki-e/upload-rust-binary-action finds the release and uploads binaries successfully

🤖 Generated with Claude Code


Note

Low Risk
Small, workflow-only change affecting how GitHub releases are created; primary risk is misconfigured API parameters causing draft release creation to fail.

Overview
Fixes the publish-cli workflow’s draft release creation to avoid accidental untagged-* releases when a release already exists for a just-pushed tag.

Replaces gh release create with a direct gh api POST /repos/{owner}/{repo}/releases, explicitly setting tag_name, target_commitish, and the changelog-derived release body before continuing with the existing release-notes enhancement and publish steps.

Written by Cursor Bugbot for commit 8ecbb58. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings February 18, 2026 13:21
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Note

Gemini is unable to generate a summary for this pull request due to the file types involved not being currently supported.

Copy link
Copy Markdown
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 addresses a persistent issue where GitHub releases are created with untagged-* tags instead of the intended version tag. The previous fix (#506) attempted to resolve this by adding --target "$GITHUB_SHA" to gh release create, but the problem persisted because when a release already exists for a tag, gh release create silently falls back to creating an untagged-* draft instead of failing. This PR bypasses the gh release create wrapper entirely by calling the GitHub API directly with explicit tag_name and target_commitish fields.

Changes:

  • Replace gh release create with direct gh api POST call to /repos/{owner}/{repo}/releases
  • Set explicit tag_name and target_commitish fields to prevent fallback to untagged-* releases
  • Add explanatory comment about why the API is called directly

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +44
BODY=$(cat /tmp/release-notes.txt)
# Use the API directly so tag_name is set explicitly (gh release create
# can produce untagged-* drafts when the tag is very recently pushed)
gh api repos/${{ github.repository }}/releases \
--method POST \
-f tag_name="$TAG_NAME" \
-f target_commitish="$GITHUB_SHA" \
-f name="$TAG_NAME" \
-f body="$BODY" \
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The multiline body content from CHANGELOG.md will not be properly passed to the GitHub API with this approach. When using BODY=$(cat /tmp/release-notes.txt) followed by -f body="$BODY", the shell will expand the variable with newlines, but gh api -f expects a string parameter and may not handle embedded newlines correctly.

The previous approach using --notes-file /tmp/release-notes.txt with gh release create properly handled multiline content by reading from a file. With the direct API call, you should use the @ syntax to read from a file: -F body=@/tmp/release-notes.txt instead of -f body="$BODY".

Note the difference:

  • -f (lowercase) sends data as form fields and expects string values
  • -F (uppercase) can read from files when prefixed with @

This will ensure the changelog content is correctly sent as the release body.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +46
# Use the API directly so tag_name is set explicitly (gh release create
# can produce untagged-* drafts when the tag is very recently pushed)
gh api repos/${{ github.repository }}/releases \
--method POST \
-f tag_name="$TAG_NAME" \
-f target_commitish="$GITHUB_SHA" \
-f name="$TAG_NAME" \
-f body="$BODY" \
-F draft=true \
--jq '.html_url'
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The workflow doesn't handle the scenario where a release already exists for the tag. According to the GitHub API documentation, POSTing to /repos/{owner}/{repo}/releases with a tag_name that already has a release will result in a 422 error.

While this is better than silently creating an untagged-* draft (which was the old behavior), the workflow will fail without a clear error message. Consider adding error handling or checking if a release already exists before attempting to create one.

One approach could be to add a check before the API call:

if gh release view "$TAG_NAME" >/dev/null 2>&1; then
  echo "Release for $TAG_NAME already exists"
  gh release view "$TAG_NAME" --json htmlUrl --jq '.htmlUrl'
else
  # create the release
fi

This would make the workflow more robust and provide clearer feedback when a release already exists.

Suggested change
# Use the API directly so tag_name is set explicitly (gh release create
# can produce untagged-* drafts when the tag is very recently pushed)
gh api repos/${{ github.repository }}/releases \
--method POST \
-f tag_name="$TAG_NAME" \
-f target_commitish="$GITHUB_SHA" \
-f name="$TAG_NAME" \
-f body="$BODY" \
-F draft=true \
--jq '.html_url'
# Check if a release for this tag already exists to avoid API 422 errors
if gh release view "$TAG_NAME" >/dev/null 2>&1; then
echo "Release for $TAG_NAME already exists:"
gh release view "$TAG_NAME" --json htmlUrl --jq '.htmlUrl'
else
# Use the API directly so tag_name is set explicitly (gh release create
# can produce untagged-* drafts when the tag is very recently pushed)
gh api repos/${{ github.repository }}/releases \
--method POST \
-f tag_name="$TAG_NAME" \
-f target_commitish="$GITHUB_SHA" \
-f name="$TAG_NAME" \
-f body="$BODY" \
-F draft=true \
--jq '.html_url'
fi

Copilot uses AI. Check for mistakes.
gh release create --draft "$TAG_NAME" \
--title "$TAG_NAME" \
--notes-file /tmp/release-notes.txt
BODY=$(cat /tmp/release-notes.txt)
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

Line 36 captures the changelog content into the BODY variable, but this variable is no longer needed if the fix for multiline content handling is applied (using -F body=@/tmp/release-notes.txt instead of -f body="$BODY"). This line can be removed to simplify the script.

Copilot uses AI. Check for mistakes.
…_name

gh release create produces untagged-* drafts when a release for the tag
already exists (e.g. created by release-plz ~60s before publish-cli runs).
Instead of using the gh CLI wrapper, call the REST API directly so that
tag_name is set explicitly and can never fall back to an auto-generated value.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@jdx jdx force-pushed the chore/use-communique branch from 7c1508e to 8ecbb58 Compare February 18, 2026 13:25
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 18, 2026

Codecov Report

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

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #508   +/-   ##
=======================================
  Coverage   77.88%   77.88%           
=======================================
  Files          48       48           
  Lines        6660     6660           
  Branches     6660     6660           
=======================================
  Hits         5187     5187           
  Misses       1109     1109           
  Partials      364      364           

☔ View full report in Codecov by Sentry.
📢 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.

@jdx jdx merged commit 87335ff into main Feb 18, 2026
9 checks passed
@jdx jdx deleted the chore/use-communique branch February 18, 2026 13:27
jdx pushed a commit that referenced this pull request Feb 18, 2026
### 🐛 Bug Fixes

- **(publish)** use gh api directly to create release with explicit
tag_name by [@jdx](https://github.com/jdx) in
[#508](#508)
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before Autofix could start.

-f name="$TAG_NAME" \
-f body="$BODY" \
-F draft=true \
--jq '.html_url'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

API POST fails with 422 when release already exists

High Severity

The PR description identifies that "something creates a GitHub release for the tag ~60 seconds before the publish-cli workflow starts." The GitHub REST API POST /repos/{owner}/{repo}/releases returns a 422 Validation Failed (already_exists) error when a release for that tag_name already exists. This means the create-release job will fail, and since build-and-publish has needs: [create-release], the entire binary upload pipeline stops — no binaries get built or uploaded at all. This is a regression from the old behavior, where gh release create at least allowed the pipeline to continue (producing an untagged-* draft).

Fix in Cursor Fix in Web

-f tag_name="$TAG_NAME" \
-f target_commitish="$GITHUB_SHA" \
-f name="$TAG_NAME" \
-f body="$BODY" \
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shell variable for body less robust than file input

Low Severity

Capturing release notes via BODY=$(cat /tmp/release-notes.txt) and passing them with -f body="$BODY" introduces unnecessary fragility — command substitution strips trailing newlines and passes potentially large content through a shell variable. The gh api command natively supports reading file content via -F body=@/tmp/release-notes.txt, which is more robust, eliminates the intermediate BODY variable, and is the idiomatic approach.

Fix in Cursor Fix in Web

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.

2 participants