Skip to content

fix: create git tag explicitly for draft releases#432

Merged
Aureliolo merged 5 commits intomainfrom
fix/draft-release-tag-creation
Mar 15, 2026
Merged

fix: create git tag explicitly for draft releases#432
Aureliolo merged 5 commits intomainfrom
fix/draft-release-tag-creation

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add explicit git tag creation step to release.yml after Release Please creates a draft release
  • GitHub doesn't create git tags for draft releases (only on publish), so Docker/CLI workflows never triggered
  • Uses RELEASE_PLEASE_TOKEN (PAT) so the tag push event triggers downstream workflows
  • Idempotent: skips if tag already exists

Context

The v0.2.3 release was stuck as a draft because no tag existed to trigger Docker/CLI workflows. The "draft": true config from PR #429 works for immutable release support, but requires this explicit tag creation step to complete the pipeline.

Release flow after this fix:

  1. Push to main → Release Please creates draft release (no tag)
  2. This new step creates and pushes the git tag via API
  3. Tag push triggers Docker (images) and CLI (binaries) workflows
  4. Both attach assets to the draft
  5. finalize-release.yml publishes the draft when both succeed

Test plan

  • Verify next release creates tag automatically after draft
  • Verify Docker/CLI workflows trigger from the tag push
  • Verify finalize-release publishes the draft after both succeed

GitHub doesn't create git tags for draft releases (only on publish).
Add a step in release.yml that creates the tag via API after Release
Please creates the draft. Uses RELEASE_PLEASE_TOKEN so the tag push
triggers downstream Docker/CLI workflows.
@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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 15, 2026

Dependency Review

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

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 15, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4c1abd17-629c-4196-9b43-57401430b8eb

📥 Commits

Reviewing files that changed from the base of the PR and between 0bad8b0 and 2dd35e3.

📒 Files selected for processing (1)
  • .github/workflows/release.yml

📝 Walkthrough

Summary by CodeRabbit

  • Chores
    • Create git tags for draft releases so downstream workflows trigger reliably.
    • Upload built CLI artifacts and checksums to draft releases idempotently to support repeatable runs.
    • Disable GoReleaser’s built-in GitHub release handling to avoid conflicts with the draft-release publishing flow.

Walkthrough

Adds a step to create a git tag/ref for Release Please draft releases via the GitHub API, adds a CLI workflow step to upload CLI artifacts to draft releases using gh, and disables GoReleaser's GitHub release handling in the CLI config.

Changes

Cohort / File(s) Summary
Release workflow
.github/workflows/release.yml
Adds "Create git tag for draft release" step: reads TAG_NAME/TAG_SHA (with fallbacks) from release-please outputs, validates values, uses RELEASE_PLEASE_TOKEN to call GitHub API to create tag object and refs; treats HTTP 201 as success, HTTP 409 as "already exists", errors on other codes.
CLI release workflow
.github/workflows/cli.yml
Updates CLI Release job messaging about missing release; adds "Upload assets to draft release" step that uses GH_TOKEN and gh release upload --clobber to upload cli/dist/*.tar.gz, cli/dist/*.zip, and cli/dist/checksums.txt, computing the tag from refs/tags/* and running after checksum validation.
GoReleaser config
cli/.goreleaser.yml
Replaces release.mode: keep-existing with release.disable: true and adds comments clarifying Release Please/workflow-managed draft releases and asset uploads (skip GoReleaser release management).

Sequence Diagram(s)

sequenceDiagram
  participant RP as Release Please
  participant CI as GitHub Actions (release.yml)
  participant GHAPI as GitHub API
  participant Down as Downstream Workflows

  RP->>CI: provide TAG_NAME & TAG_SHA
  CI->>GHAPI: GET /git/ref/tags/TAG_NAME
  alt tag exists
    GHAPI-->>CI: 200 Tag found
  else tag missing
    CI->>GHAPI: POST /git/tags (create tag object @ TAG_SHA)
    GHAPI-->>CI: 201 Tag created
    CI->>GHAPI: POST /git/refs (create refs/tags/TAG_NAME)
    GHAPI-->>CI: 201 Ref created
  end
  CI->>Down: tag/ref becomes available
Loading
sequenceDiagram
  participant CI as GitHub Actions (cli.yml)
  participant Build as Build artifacts
  participant GHCLI as gh CLI
  participant Release as GitHub Release (draft)

  CI->>Build: produce cli/dist/*.tar.gz, *.zip, checksums.txt
  CI->>GHCLI: gh release upload <TAG> --clobber <artifacts>
  GHCLI->>Release: upload/replace assets
  Release-->>CI: confirmation
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding explicit git tag creation for draft releases, which is the core objective of this pull request.
Description check ✅ Passed The description is clearly related to the changeset, providing context about the problem (draft releases not triggering workflows), the solution (explicit tag creation), and expected workflow integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/draft-release-tag-creation
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/draft-release-tag-creation
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 15, 2026

Greptile Summary

This PR fixes a broken release pipeline by explicitly creating the git tag after Release Please generates a draft release, since GitHub only auto-creates tags when a release is published — not when it's left as a draft.

Key changes:

  • release.yml: A new step uses curl against the GitHub refs API to create the tag immediately after Release Please creates the draft. The implementation handles idempotency correctly (HTTP 201 = created, HTTP 409/422 "Reference already exists" = skip), guards against empty metadata from Release Please, and uses a PAT (RELEASE_PLEASE_TOKEN) so the tag push event can trigger downstream workflows (which GITHUB_TOKEN pushes cannot do).
  • cli.yml: GoReleaser's built-in release management is bypassed in favour of an explicit gh release upload --clobber step. This is necessary because GoReleaser's GitHub API call returns 404 when querying a draft release by tag. The fallback message after the 5-minute release-wait timeout is also tightened to make it clear the subsequent upload step will fail.
  • cli/.goreleaser.yml: release.mode: keep-existing replaced with release.disable: true — GoReleaser is now purely a build tool; Release Please and gh release upload own the GitHub Release lifecycle entirely.

Confidence Score: 4/5

  • Safe to merge — the changes are well-structured and correctly address the root cause, with only minor hardening opportunities remaining.
  • The core logic is sound: using curl with explicit HTTP-code inspection cleanly sidesteps the pipefail/prefix-matching/stdout issues flagged in previous review rounds. The guard for empty TAG_NAME/TAG_SHA and the idempotent 422 handling are both correct. The two remaining items (JSON payload via string interpolation, and unguarded glob expansion) are both low-severity style improvements rather than blocking bugs, hence 4/5 rather than 5/5.
  • .github/workflows/release.yml (curl JSON payload construction) and .github/workflows/cli.yml (glob expansion guard in upload step)

Important Files Changed

Filename Overview
.github/workflows/release.yml Adds an explicit git tag creation step using curl against the GitHub refs API. The implementation correctly handles the 422 "Reference already exists" idempotency case and guards against empty metadata. Minor: curl JSON payload is constructed via shell string interpolation instead of jq, which could break if tag names ever contain JSON-special characters.
.github/workflows/cli.yml Adds an asset upload step via gh release upload with --clobber after GoReleaser builds artifacts. Correctly replaces GoReleaser's own release management. Minor: glob patterns in the upload command are not guarded against zero-match expansion.
cli/.goreleaser.yml Changes release.mode: keep-existing to release.disable: true so GoReleaser no longer attempts to interact with GitHub Releases (which returns 404 for drafts queried by tag). Release management is now fully delegated to Release Please and gh release upload.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: .github/workflows/release.yml
Line: 47-52

Comment:
**JSON payload built via direct string interpolation**

`TAG_NAME` and `TAG_SHA` are interpolated directly into the JSON string. Although Release Please always outputs clean semver tags and hex SHAs today, a tag name containing a `"`, `\`, or newline character would silently produce malformed JSON and the API call would fail with a cryptic `400` or `422` response — bypassing the readable error handling below.

Using `jq` to construct the payload is idiomatic and prevents this entire class of error:

```suggestion
          HTTP_CODE=$(curl -s -o "$RESPONSE_FILE" -w '%{http_code}' \
            -X POST \
            -H "Authorization: token $GH_TOKEN" \
            -H "Accept: application/vnd.github+json" \
            "https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs" \
            -d "$(jq -n --arg ref "refs/tags/$TAG_NAME" --arg sha "$TAG_SHA" '{"ref":$ref,"sha":$sha}')")
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/cli.yml
Line: 297-301

Comment:
**Unquoted globs silently pass literal strings on zero matches**

If bash glob expansion for `cli/dist/*.tar.gz` or `cli/dist/*.zip` matches no files (e.g., if GoReleaser's archive naming changes), bash passes the literal glob strings as filenames to `gh release upload`. The result is a confusing `no such file: cli/dist/*.tar.gz` error rather than a clear build failure. Setting `failglob` before the command makes this fail loudly and immediately at the expansion step:

```suggestion
          TAG="${GITHUB_REF#refs/tags/}"
          # gh release upload finds draft releases (unlike GoReleaser's API).
          # --clobber overwrites if assets already exist (idempotent on re-run).
          shopt -s failglob
          gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
            cli/dist/*.tar.gz cli/dist/*.zip cli/dist/checksums.txt
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 2dd35e3

GoReleaser can't find draft releases by tag (GitHub API limitation:
GET /releases/tags/{tag} returns 404 for drafts). This caused it to
create a duplicate published release alongside the draft.

Fix: set release.skip: true in .goreleaser.yml so GoReleaser only
builds artifacts. Upload assets via gh release upload which CAN find
draft releases. Idempotent with --clobber on re-runs.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 33-36: The workflow reads release-please outputs using
path-prefixed keys steps.release.outputs['.--tag_name'] and ['.--sha'] which are
empty when the component path is root; change the resolution to try the
path-prefixed key then fall back to the non-prefixed key (e.g., resolve TAG_NAME
from steps.release.outputs['.--tag_name'] || steps.release.outputs['tag_name']
and TAG_SHA similarly), and add a guard immediately after resolution that fails
the job fast (exit non-zero) if either TAG_NAME or TAG_SHA is empty to avoid
creating invalid refs; update references to the resolved variables (TAG_NAME,
TAG_SHA) accordingly.
- Around line 37-43: Replace the unsafe check-then-create sequence that calls gh
api "repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG_NAME" then POST to
"repos/$GITHUB_REPOSITORY/git/refs" with an atomic create-and-handle-conflict
flow: use the canonical ref path when you need to probe (GET
"repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG_NAME") or, better, attempt the POST
to create the ref (POST "repos/$GITHUB_REPOSITORY/git/refs" with
ref=refs/tags/$TAG_NAME and sha=$TAG_SHA) and catch a 409 Conflict response and
treat it as success (log "Tag already exists") instead of failing the job;
update the gh api invocations around TAG_NAME and TAG_SHA to implement this
retry/error-handling behavior so concurrent runs do not race.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 31b57cf9-29be-4d07-a4bd-2f0e67fdccd4

📥 Commits

Reviewing files that changed from the base of the PR and between 440c540 and 9227dfd.

📒 Files selected for processing (1)
  • .github/workflows/release.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (python)
🔇 Additional comments (1)
.github/workflows/release.yml (1)

30-31: Good conditional gate for release-only execution.

Running this only when releases_created is true avoids unnecessary API calls and keeps the workflow focused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/.goreleaser.yml`:
- Around line 39-43: The release configuration uses an invalid key `skip: true`;
change the `release` block to use GoReleaser's supported key by replacing `skip:
true` with `disable: true` so GoReleaser will be disabled (leaving Release
Please to manage releases); locate the `release` section in the CLI GoReleaser
config (the block containing `skip: true`) and update the key name to `disable`
while keeping the boolean value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f032650b-573b-4230-b96a-0f571e62d69d

📥 Commits

Reviewing files that changed from the base of the PR and between 9227dfd and 2923170.

📒 Files selected for processing (2)
  • .github/workflows/cli.yml
  • cli/.goreleaser.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (python)
🔇 Additional comments (1)
.github/workflows/cli.yml (1)

293-302: Idempotent asset upload implementation looks good.

Line 300’s --clobber makes reruns safe, and the step cleanly scopes uploads to the current tag/repo.

- goreleaser: use release.disable (not skip) — correct v2 key (CodeRabbit)
- release.yml: use unprefixed output keys with path-prefixed fallback (CodeRabbit)
- release.yml: guard against empty TAG_NAME/TAG_SHA (Greptile)
- release.yml: exact ref match to avoid prefix-matching false positives (Greptile)
- release.yml: suppress stdout noise in idempotency check (Greptile)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
.github/workflows/release.yml (1)

44-53: ⚠️ Potential issue | 🟠 Major

Make tag creation atomic to avoid flaky release failures.

Line 46–53 still uses check-then-create; concurrent runs can pass the check and then fail on POST when the ref is created by another run. Treat “already exists” create errors as success.

Suggested fix
-          # Idempotent: check if exact tag already exists.
-          # GitHub refs API uses prefix matching, so we must verify the exact ref.
-          if gh api "repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG_NAME" --jq '.[].ref, .ref' 2>/dev/null \
-              | grep -qxF "refs/tags/$TAG_NAME"; then
-            echo "Tag $TAG_NAME already exists — skipping."
-          else
-            gh api "repos/$GITHUB_REPOSITORY/git/refs" \
-              --method POST \
-              --field "ref=refs/tags/$TAG_NAME" \
-              --field "sha=$TAG_SHA"
-            echo "Tag $TAG_NAME created successfully."
-          fi
+          # Idempotent + race-safe: create first, treat "already exists" as success.
+          create_err="$(
+            gh api "repos/$GITHUB_REPOSITORY/git/refs" \
+              --method POST \
+              --field "ref=refs/tags/$TAG_NAME" \
+              --field "sha=$TAG_SHA" 2>&1
+          )" || {
+            if grep -qiE 'already exists|HTTP 409|HTTP 422' <<<"$create_err"; then
+              echo "Tag $TAG_NAME already exists — skipping."
+              exit 0
+            fi
+            echo "$create_err"
+            exit 1
+          }
+          echo "Tag $TAG_NAME created successfully."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 44 - 53, Replace the current
check-then-create pattern for tag creation (the gh api GET of refs for TAG_NAME
followed by the gh api POST to "repos/$GITHUB_REPOSITORY/git/refs" with
ref=refs/tags/$TAG_NAME and sha=$TAG_SHA) with an atomic create-and-handle
approach: attempt the POST unconditionally and consider the operation successful
if it returns success or fails with an error indicating the ref already exists
(treat the "already exists"/HTTP 422 conflict style response from the POST as
success), otherwise fail the job; ensure your handling inspects the POST
response/error and only treats genuine errors as failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/release.yml:
- Around line 44-53: Replace the current check-then-create pattern for tag
creation (the gh api GET of refs for TAG_NAME followed by the gh api POST to
"repos/$GITHUB_REPOSITORY/git/refs" with ref=refs/tags/$TAG_NAME and
sha=$TAG_SHA) with an atomic create-and-handle approach: attempt the POST
unconditionally and consider the operation successful if it returns success or
fails with an error indicating the ref already exists (treat the "already
exists"/HTTP 422 conflict style response from the POST as success), otherwise
fail the job; ensure your handling inspects the POST response/error and only
treats genuine errors as failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fa4d375c-65c1-494f-92e5-46ce4199fe60

📥 Commits

Reviewing files that changed from the base of the PR and between 2923170 and d901532.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • cli/.goreleaser.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (python)
🔇 Additional comments (1)
cli/.goreleaser.yml (1)

39-43: Good alignment with draft-release ownership.

release.disable: true cleanly prevents GoReleaser from competing with the Release Please draft flow.

- release.yml: replace check-then-create with atomic POST + handle 422
  (avoids TOCTOU race and prefix-matching pitfalls)
- cli.yml: update stale fallback message that referenced GoReleaser
  creating releases (now disabled)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
.github/workflows/release.yml (1)

47-60: ⚠️ Potential issue | 🟠 Major

Tighten create-ref status handling to avoid false “already exists” success.

Treating every 422 as idempotent success can hide real validation failures (bad ref/sha), and conflict-style responses may also appear as 409. Handle 409 explicitly and only treat 422 as success when the error body clearly indicates an existing ref.

Suggested patch
-          HTTP_CODE=$(gh api "repos/$GITHUB_REPOSITORY/git/refs" \
+          RESPONSE="$(gh api "repos/$GITHUB_REPOSITORY/git/refs" \
             --method POST \
             --field "ref=refs/tags/$TAG_NAME" \
             --field "sha=$TAG_SHA" \
-            --silent --include 2>&1 | head -1 | grep -oE '[0-9]{3}' || echo "000")
+            --silent --include 2>&1 || true)"
+          HTTP_CODE="$(printf '%s\n' "$RESPONSE" | head -1 | grep -oE '[0-9]{3}' || echo "000")"

           case "$HTTP_CODE" in
             201) echo "Tag $TAG_NAME created successfully." ;;
-            422) echo "Tag $TAG_NAME already exists — skipping." ;;
+            409) echo "Tag $TAG_NAME already exists — skipping." ;;
+            422)
+              if printf '%s\n' "$RESPONSE" | grep -qiE 'already exists|Reference already exists'; then
+                echo "Tag $TAG_NAME already exists — skipping."
+              else
+                echo "::error::Validation failed creating tag $TAG_NAME (HTTP 422)"
+                printf '%s\n' "$RESPONSE"
+                exit 1
+              fi
+              ;;
             *)
               echo "::error::Failed to create tag $TAG_NAME (HTTP $HTTP_CODE)"
               exit 1
               ;;
           esac
For GitHub REST API "Create a reference" (POST /repos/{owner}/{repo}/git/refs), which status codes are documented for "reference already exists", and what non-"already exists" 422 validation failures are documented?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 47 - 60, Capture both the full gh
api response body and the HTTP status instead of only the status (variables
HTTP_CODE and RESPONSE_BODY), then update the case logic in the create-ref block
to: treat 201 as success; treat 409 explicitly as "Tag already exists —
skipping"; for 422, inspect RESPONSE_BODY for the specific "Reference already
exists" (or equivalent GitHub error message) and only skip when that exact
message is present, otherwise treat 422 as a real validation error and fail;
ensure you reference the existing variables TAG_NAME and TAG_SHA and the gh api
POST that creates refs when making these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/release.yml:
- Around line 47-60: Capture both the full gh api response body and the HTTP
status instead of only the status (variables HTTP_CODE and RESPONSE_BODY), then
update the case logic in the create-ref block to: treat 201 as success; treat
409 explicitly as "Tag already exists — skipping"; for 422, inspect
RESPONSE_BODY for the specific "Reference already exists" (or equivalent GitHub
error message) and only skip when that exact message is present, otherwise treat
422 as a real validation error and fail; ensure you reference the existing
variables TAG_NAME and TAG_SHA and the gh api POST that creates refs when making
these changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3e7f90fe-bdf5-4cf6-ad40-a0e96d7dff56

📥 Commits

Reviewing files that changed from the base of the PR and between d901532 and 0bad8b0.

📒 Files selected for processing (2)
  • .github/workflows/cli.yml
  • .github/workflows/release.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (python)
🔇 Additional comments (3)
.github/workflows/cli.yml (2)

275-275: Clear fallback message improvement.

The updated message sets correct expectations about where failure will occur if the draft release is still missing.


293-301: Draft asset upload step is correctly implemented and idempotent.

Using gh release upload with --clobber and repo-scoped auth is a solid fit for reruns and draft-release asset attachment.

.github/workflows/release.yml (1)

33-40: Output fallback and fail-fast guard are solid.

Resolving both root/path-prefixed outputs and failing on missing metadata is the right hardening move.

…reation

Use curl to capture both HTTP status and response body. Only treat 422
as idempotent skip when the body contains "Reference already exists".
Genuine validation errors (e.g., invalid SHA) now fail properly.
Also handles 409 Conflict explicitly.
@Aureliolo Aureliolo merged commit 1f5120e into main Mar 15, 2026
36 of 37 checks passed
@Aureliolo Aureliolo deleted the fix/draft-release-tag-creation branch March 15, 2026 09:04
Comment on lines +47 to +52
HTTP_CODE=$(curl -s -o "$RESPONSE_FILE" -w '%{http_code}' \
-X POST \
-H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs" \
-d "{\"ref\":\"refs/tags/$TAG_NAME\",\"sha\":\"$TAG_SHA\"}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSON payload built via direct string interpolation

TAG_NAME and TAG_SHA are interpolated directly into the JSON string. Although Release Please always outputs clean semver tags and hex SHAs today, a tag name containing a ", \, or newline character would silently produce malformed JSON and the API call would fail with a cryptic 400 or 422 response — bypassing the readable error handling below.

Using jq to construct the payload is idiomatic and prevents this entire class of error:

Suggested change
HTTP_CODE=$(curl -s -o "$RESPONSE_FILE" -w '%{http_code}' \
-X POST \
-H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs" \
-d "{\"ref\":\"refs/tags/$TAG_NAME\",\"sha\":\"$TAG_SHA\"}")
HTTP_CODE=$(curl -s -o "$RESPONSE_FILE" -w '%{http_code}' \
-X POST \
-H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs" \
-d "$(jq -n --arg ref "refs/tags/$TAG_NAME" --arg sha "$TAG_SHA" '{"ref":$ref,"sha":$sha}')")
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/release.yml
Line: 47-52

Comment:
**JSON payload built via direct string interpolation**

`TAG_NAME` and `TAG_SHA` are interpolated directly into the JSON string. Although Release Please always outputs clean semver tags and hex SHAs today, a tag name containing a `"`, `\`, or newline character would silently produce malformed JSON and the API call would fail with a cryptic `400` or `422` response — bypassing the readable error handling below.

Using `jq` to construct the payload is idiomatic and prevents this entire class of error:

```suggestion
          HTTP_CODE=$(curl -s -o "$RESPONSE_FILE" -w '%{http_code}' \
            -X POST \
            -H "Authorization: token $GH_TOKEN" \
            -H "Accept: application/vnd.github+json" \
            "https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs" \
            -d "$(jq -n --arg ref "refs/tags/$TAG_NAME" --arg sha "$TAG_SHA" '{"ref":$ref,"sha":$sha}')")
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +297 to +301
TAG="${GITHUB_REF#refs/tags/}"
# gh release upload finds draft releases (unlike GoReleaser's API).
# --clobber overwrites if assets already exist (idempotent on re-run).
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
cli/dist/*.tar.gz cli/dist/*.zip cli/dist/checksums.txt
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unquoted globs silently pass literal strings on zero matches

If bash glob expansion for cli/dist/*.tar.gz or cli/dist/*.zip matches no files (e.g., if GoReleaser's archive naming changes), bash passes the literal glob strings as filenames to gh release upload. The result is a confusing no such file: cli/dist/*.tar.gz error rather than a clear build failure. Setting failglob before the command makes this fail loudly and immediately at the expansion step:

Suggested change
TAG="${GITHUB_REF#refs/tags/}"
# gh release upload finds draft releases (unlike GoReleaser's API).
# --clobber overwrites if assets already exist (idempotent on re-run).
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
cli/dist/*.tar.gz cli/dist/*.zip cli/dist/checksums.txt
TAG="${GITHUB_REF#refs/tags/}"
# gh release upload finds draft releases (unlike GoReleaser's API).
# --clobber overwrites if assets already exist (idempotent on re-run).
shopt -s failglob
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
cli/dist/*.tar.gz cli/dist/*.zip cli/dist/checksums.txt
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/cli.yml
Line: 297-301

Comment:
**Unquoted globs silently pass literal strings on zero matches**

If bash glob expansion for `cli/dist/*.tar.gz` or `cli/dist/*.zip` matches no files (e.g., if GoReleaser's archive naming changes), bash passes the literal glob strings as filenames to `gh release upload`. The result is a confusing `no such file: cli/dist/*.tar.gz` error rather than a clear build failure. Setting `failglob` before the command makes this fail loudly and immediately at the expansion step:

```suggestion
          TAG="${GITHUB_REF#refs/tags/}"
          # gh release upload finds draft releases (unlike GoReleaser's API).
          # --clobber overwrites if assets already exist (idempotent on re-run).
          shopt -s failglob
          gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
            cli/dist/*.tar.gz cli/dist/*.zip cli/dist/checksums.txt
```

How can I resolve this? If you propose a fix, please make it concise.

Aureliolo added a commit that referenced this pull request Mar 15, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.2.4](v0.2.3...v0.2.4)
(2026-03-15)


### Bug Fixes

* attach cosign signatures and provenance bundle to release assets
([#438](#438))
([f191a4d](f191a4d))
* create git tag explicitly for draft releases
([#432](#432))
([1f5120e](1f5120e))
* docker healthcheck, CI optimization, and container hardening
([#436](#436))
([4d32bca](4d32bca))
* ensure security headers on all HTTP responses
([#437](#437))
([837f2fc](837f2fc))
* make install scripts usable immediately without terminal restart
([#433](#433))
([b45533c](b45533c))
* migrate pids_limit to deploy.resources.limits.pids
([#439](#439))
([66b94fd](66b94fd))


### Refactoring

* redesign release notes layout
([#434](#434))
([239aaf7](239aaf7))


### Maintenance

* **site:** replace hero CTA with license link and scroll arrow
([#440](#440))
([56af41c](56af41c))
* **web:** adopt @vue/tsconfig preset
([#435](#435))
([7d4b214](7d4b214))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Aureliolo added a commit that referenced this pull request Mar 15, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.2.4](v0.2.3...v0.2.4)
(2026-03-15)


### Bug Fixes

* attach cosign signatures and provenance bundle to release assets
([#438](#438))
([f191a4d](f191a4d))
* create git tag explicitly for draft releases
([#432](#432))
([1f5120e](1f5120e))
* docker healthcheck, CI optimization, and container hardening
([#436](#436))
([4d32bca](4d32bca))
* ensure security headers on all HTTP responses
([#437](#437))
([837f2fc](837f2fc))
* make install scripts usable immediately without terminal restart
([#433](#433))
([b45533c](b45533c))
* migrate pids_limit to deploy.resources.limits.pids
([#439](#439))
([66b94fd](66b94fd))
* use cosign --bundle flag for checksums signing
([#443](#443))
([19735b9](19735b9))


### Refactoring

* redesign release notes layout
([#434](#434))
([239aaf7](239aaf7))


### Maintenance

* **main:** release 0.2.4
([#431](#431))
([63b03c4](63b03c4))
* remove stale v0.2.4 changelog section from failed release
([#446](#446))
([769de10](769de10))
* reset version to 0.2.3 for re-release
([#444](#444))
([8579993](8579993))
* **site:** replace hero CTA with license link and scroll arrow
([#440](#440))
([56af41c](56af41c))
* **web:** adopt @vue/tsconfig preset
([#435](#435))
([7d4b214](7d4b214))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
This was referenced Mar 15, 2026
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.

1 participant