fix: create git tag explicitly for draft releases#432
Conversation
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.
|
Note Gemini is unable to generate a summary for this pull request due to the file types involved not being currently supported. |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds 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 Changes
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
📝 Coding Plan
Comment |
Greptile SummaryThis 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:
Confidence Score: 4/5
Important Files Changed
Prompt To Fix All With AIThis 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.
There was a problem hiding this comment.
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
📒 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_createdis true avoids unnecessary API calls and keeps the workflow focused.
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
.github/workflows/cli.ymlcli/.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
--clobbermakes 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)
There was a problem hiding this comment.
♻️ Duplicate comments (1)
.github/workflows/release.yml (1)
44-53:⚠️ Potential issue | 🟠 MajorMake 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
📒 Files selected for processing (2)
.github/workflows/release.ymlcli/.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: truecleanly 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)
There was a problem hiding this comment.
♻️ Duplicate comments (1)
.github/workflows/release.yml (1)
47-60:⚠️ Potential issue | 🟠 MajorTighten create-ref status handling to avoid false “already exists” success.
Treating every
422as idempotent success can hide real validation failures (bad ref/sha), and conflict-style responses may also appear as409. Handle409explicitly and only treat422as 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 ;; esacFor 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
📒 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 uploadwith--clobberand 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.
| 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\"}") |
There was a problem hiding this 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:
| 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.| 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 |
There was a problem hiding this 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:
| 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.🤖 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).
🤖 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).
Summary
release.ymlafter Release Please creates a draft releaseRELEASE_PLEASE_TOKEN(PAT) so the tag push event triggers downstream workflowsContext
The
v0.2.3release was stuck as a draft because no tag existed to trigger Docker/CLI workflows. The"draft": trueconfig from PR #429 works for immutable release support, but requires this explicit tag creation step to complete the pipeline.Release flow after this fix:
finalize-release.ymlpublishes the draft when both succeedTest plan