Skip to content

fix(ci): prevent recreating consumed releases - #377

Merged
xiufengsun merged 2 commits into
xiufengsun:mainfrom
paveezheng:codex/fix-release-immutability
Jul 27, 2026
Merged

fix(ci): prevent recreating consumed releases#377
xiufengsun merged 2 commits into
xiufengsun:mainfrom
paveezheng:codex/fix-release-immutability

Conversation

@paveezheng

@paveezheng paveezheng commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Prevent deleted GitHub releases from being recreated with an existing version tag, avoiding same-version assets with different Homebrew checksums.

Scope

  • CLI (src/)
  • Dashboard (dashboard/)
  • macOS app (TokenTrackerBar/)
  • Windows app (TokenTrackerWin/)
  • Docs / CI / config

Checklist

  • npm test passes
  • New user-facing strings go through dashboard/src/content/copy.csv (no UI strings added)
  • Commits follow conventional style (feat: / fix: / refactor: / docs: / chore: / test: / ci:)
  • PR description explains why, not just what

Testing

  • node --test test/release-dmg-workflow.test.js — 19/19 passed
  • npm test — 1888 passed, 5 failed because the local environment is missing the existing yauzl dependency

Behavior

  • Existing draft release: may resume from the matching commit.
  • Existing published release: rejected.
  • Missing release with no tag: creates a new draft release.
  • Missing release with an existing tag: rejected and requires a version bump.

Out of scope

The Homebrew tap workflow lives in a separate repository and is not changed by this PR.

Summary by CodeRabbit

  • Bug Fixes

    • Release automation now detects when a version tag already exists while its corresponding release is missing, and stops instead of attempting to recreate draft release assets.
    • Updated failure guidance to instruct bumping the version rather than reusing/reconstructing the existing tag.
  • Tests

    • Added a workflow test to confirm the guard for the existing-tag/missing-release case appears before any tag or draft release creation steps.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e77621a3-2650-4782-8701-ece0519144c8

📥 Commits

Reviewing files that changed from the base of the PR and between bc7f229 and 9c0cbda.

📒 Files selected for processing (1)
  • test/release-dmg-workflow.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/release-dmg-workflow.test.js

📝 Walkthrough

Walkthrough

The release workflow now fails when a version tag exists without its draft release instead of recreating release assets. A workflow-content test verifies the guard message, version-bump instruction, and ordering before draft release creation.

Changes

Release tag guard

Layer / File(s) Summary
Reject existing tags without releases
.github/workflows/release-dmg.yml, test/release-dmg-workflow.test.js
The workflow rejects missing releases when the version tag already exists, and tests verify the guard appears before replacement draft creation and instructs users to bump the version.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: mm7894215

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the CI fix to stop recreating consumed releases with an existing tag.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release-dmg.yml:
- Around line 116-125: Update the existing-release and tag lookup logic around
gh release view and the git ls-remote check to distinguish “not found” from API,
authentication, or network failures. Continue only when the lookup explicitly
indicates the release or tag is absent; otherwise emit the lookup error and exit
before creating refs or releases. Preserve the current rejection behavior when
an existing release or tag is found.

In `@test/release-dmg-workflow.test.js`:
- Around line 167-190: Update the test “missing releases cannot be recreated
from an existing version tag” to locate the git/refs tag-creation command and
assert that `missingReleaseGuard` precedes it, in addition to the existing `gh
release create` ordering assertion. Keep the current rejection-message checks
unchanged and ensure both creation commands are guarded.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ea1949f1-124d-4a55-9847-7f1164884392

📥 Commits

Reviewing files that changed from the base of the PR and between 1898933 and bc7f229.

📒 Files selected for processing (2)
  • .github/workflows/release-dmg.yml
  • test/release-dmg-workflow.test.js

Comment on lines +116 to 125
# A tag without a release means this version has already been used.
# Refuse to recreate it even when the tag points to GITHUB_SHA:
# rebuilding the same source can still produce a different DMG
# checksum and leave package-manager metadata pinned to the old one.
if tag_ref=$(git ls-remote --exit-code --tags origin "refs/tags/$tag" 2>/dev/null); then
tag_sha=$(awk 'NR == 1 { print $1 }' <<<"$tag_ref")
echo "::error::Release $tag is missing, but its tag already exists at $tag_sha."
echo "::error::This version is already consumed; bump the version instead of recreating it."
exit 1
fi

@coderabbitai coderabbitai Bot Jul 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate workflow =="
git ls-files .github/workflows/release-dmg.yml || true

echo "== workflow relevant section =="
if [ -f .github/workflows/release-dmg.yml ]; then
  nl -ba .github/workflows/release-dmg.yml | sed -n '1,180p'
fi

echo "== search related release/tag commands =="
rg -n "gh release view|git ls-remote|recreate|release" .github/workflows/release-dmg.yml .github/workflows 2>/dev/null || true

Repository: mm7894215/TokenTracker

Length of output: 288


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file existence and lines =="
[ -f .github/workflows/release-dmg.yml ] && wc -l .github/workflows/release-dmg.yml

echo "== first 220 lines of workflow =="
awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/release-dmg.yml | sed -n '1,220p'

echo "== search related commands =="
awk '/gh release view|git ls-remote|gh release create|gpgsign|recreate|tag_ref|tag_sha|::error::|exit 1/' .github/workflows/release-dmg.yml

Repository: mm7894215/TokenTracker

Length of output: 14496


🌐 Web query:

GitHub CLI gh release view not found exit code

💡 Result:

When you run the GitHub CLI command gh release view and the specified release (or the "latest" release, if no argument is provided) is not found, the command exits with exit code 1 [1]. In the GitHub CLI, an exit code of 1 generally signifies a generic error [2][3]. The tool does not currently provide a unique, dedicated exit code specifically to distinguish a "not found" (404) error from other types of failures [1][4]. Key takeaways for handling this behavior in automation or scripts: 1. Consistency: You should treat an exit code of 1 as a signal that the command failed [2]. 2. Brittle Workarounds: Because the exit code is not unique, some users rely on parsing the command's standard error (stderr) output to specifically check for strings like "not found" or "404" [5][4]. However, this is considered brittle because the error message format is subject to change in future updates to the CLI [4]. 3. Best Practices: If you are automating workflows (e.g., in GitHub Actions), it is recommended to implement retry logic with exponential backoff if you suspect the issue might be due to API eventual consistency (where a release has just been created and is not yet visible to the API) [6][5]. For further details on GitHub CLI exit conventions, you can run gh help exit-codes in your terminal [2][7].

Citations:


🌐 Web query:

git ls-remote --exit-code return code no matching ref

💡 Result:

When you use the --exit-code option with the git ls-remote command, Git will return an exit status of 2 if no matching references (refs) are found in the remote repository [1][2]. Normally, the git ls-remote command returns an exit status of 0 as long as it successfully establishes communication with the remote repository, regardless of whether it finds any matching references [1][3]. By adding --exit-code, you enable a specific check that allows you to programmatically determine if specific branches or tags exist on a remote [4]. If at least one matching reference is found, the command will exit with a status of 0 [1][5]. This behavior is often used in shell scripts to check for the existence of a branch: if git ls-remote --exit-code --heads "$REPO_URL" "$BRANCH_NAME" > /dev/null 2>&1; then echo "The branch exists." else echo "The branch does not exist or an error occurred." fi Note that because the command returns 2 when no matches are found, it is important to handle this non-zero exit code in your scripts, as it will trigger an error in environments where non-zero exit codes cause script execution to halt [4][6].

Citations:


🌐 Web query:

GitHub CLI gh release create 422 release already exists already exists

💡 Result:

The error "HTTP 422: Validation Failed" with "release already exists" (or "tag_name already exists") when running gh release create indicates that a GitHub release associated with that specific tag already exists in the repository [1][2][3][4]. Because the GitHub Releases API requires unique tag names for releases [5][6], the gh CLI cannot create a new release using a tag that is already linked to an existing release [1][2][3]. Common causes and solutions include: 1. Attempting to create a release for an existing tag: If you intended to update an existing release rather than create a new one, use gh release edit instead of gh release create [7]. 2. Concurrent CI/CD processes: If you are running multiple automated jobs that trigger release creation, they may be racing to create the same release [3]. Ensure your workflow logic only allows one process to handle the release creation or update [3]. 3. Pre-release confusion: If the tag is already associated with a pre-release, gh release create will still fail with this error because the tag is technically "in use" [1]. To promote a pre-release to a full release, use gh release edit --prerelease=false. 4. Duplicate assets: While not the primary cause of an "already exists" error on the tag itself, ensure you are not accidentally trying to upload duplicate filenames to the same release, which can cause related validation errors [8][9]. You can verify the status of the tag by checking the repository releases in the GitHub UI or by running gh release list to see if the tag is already present.

Citations:


🌐 Web query:

GitHub CLI gh release view GitHub JSON 404

💡 Result:

When running the GitHub CLI command gh release view with the --json flag, receiving an HTTP 404: Not Found error typically indicates that the CLI cannot find the requested release or lacks the necessary permissions to access it [1][2]. Common causes for this 404 error include: 1. Requesting a "latest" release when none exist: If you run gh release view without a specific tag argument, the command attempts to fetch the "latest" release [1][3]. If the repository has no formal releases (or only has pre-releases, which are not considered "latest" by GitHub), the API returns a 404 [1]. 2. Insufficient repository permissions: If you are using an authentication token (such as a Personal Access Token or GITHUB_TOKEN in GitHub Actions) that does not have access to the target repository, the API may return a 404 to avoid confirming the existence of a private repository to unauthorized users [2][4]. 3. Invalid or non-existent tag: If you specify a tag (e.g., gh release view v1.0.0), ensure the tag exists and is correctly typed. If it does not exist in the repository, the API returns a 404. To troubleshoot, you can: - Verify your authentication: Ensure the account or token used has sufficient permissions for the repository [2][4]. - Specify a tag: If the "latest" release is not found, try providing a specific tag name: gh release view <tag_name> --json ... [1][3]. - Check for repository visibility: If working in GitHub Actions, ensure you are using a token with sufficient scope if the repository is private or in a different organization [4]. If you are using --json to inspect release data, note that the command filters available fields [3][5]. You can view the full list of available JSON fields for a command by running it without any arguments after the --json flag (e.g., gh release view --json) [3][5].

Citations:


Fail closed when gh release view lookup fails.

At lines 85 and 120, any nonzero lookup result, including API/authentication/network errors, can skip the existing-release/tag checks and fall through to the creation path. Only accept absences where the lookup output/error indicates release/tag not found; abort otherwise before creating refs or releases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-dmg.yml around lines 116 - 125, Update the
existing-release and tag lookup logic around gh release view and the git
ls-remote check to distinguish “not found” from API, authentication, or network
failures. Continue only when the lookup explicitly indicates the release or tag
is absent; otherwise emit the lookup error and exit before creating refs or
releases. Preserve the current rejection behavior when an existing release or
tag is found.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The script runs under set -euo pipefail, so every transient-failure path here fails closed: if git ls-remote errors while the tag actually exists, the subsequent git/refs POST returns 422 and the job fails; nothing gets created on an error path. Worst case is a misleading error message on a transient outage, where a re-run recovers. Keeping the lookup logic as is.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@mm7894215, understood. One clarification: set -e is deliberately suppressed for commands evaluated as an if condition, so either lookup can return nonzero and still enter the fallback path.

A broad outage will likely cause the later git/refs POST to fail as you describe, but that is not a strict fail-closed guarantee: a transient/read-specific gh release view failure followed by successful write requests can still create a tag and draft release. The current behavior is therefore an accepted retry-risk tradeoff rather than an error-path abort.

Comment thread test/release-dmg-workflow.test.js
@xiufengsun
xiufengsun merged commit 69970bd into xiufengsun:main Jul 27, 2026
7 checks passed
@xiufengsun

Copy link
Copy Markdown
Owner

Thanks @Yanzzp999! Solid guard against checksum drift on consumed release tags — merged with a small test-ordering assertion added on top.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants