Skip to content

fix(release): find an existing draft by listing, upload to it by id - #535

Merged
Leo310 merged 3 commits into
mainfrom
fix/release-draft-lookup
Sep 28, 2026
Merged

Leo310 merged 3 commits into
mainfrom
fix/release-draft-lookup

Conversation

@Leo310

@Leo310 Leo310 commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

What

Re-pointing the unpublished 2.3.0 tag created a second draft instead of updating the first. GitHub's releases/tags/<tag> endpoint never returns drafts, so the workflow's lookup 404'd and took the "no release yet" path. The mid-upload "was it just published?" re-check used the same endpoint, so it would also have failed on a draft.

  • Find the tag's release by listing releases (--paginate, with the tag passed to jq via --arg):
    • none → create the draft, as before
    • more than one → fail and ask for the strays to be deleted
    • one published → refuse, as before
    • one draft → update it by id
  • Draft notes are set with PATCH releases/<id>, restating tag_name. A PATCH without it renames a draft's tag to untagged-<hash>, verified against a draft on an existing tag, which detaches the draft from its tag. That is probably also how 2.1.1 was published as untagged-<hash>.
  • Assets are replaced by id through the uploads API (delete the same-named asset, then upload), with a paginated lookup. gh release upload <tag> also failed to find a draft in testing, so it's no longer used.
  • The job has a per-tag concurrency group (queued, not cancelled), so two pushes of the same tag can't both create a draft.

How I tested it

  • Ran the step body with bash three times in a row against a scratch draft on an existing tag (2.3.0-beta.1, whose release had been deleted):
    • Every run updated that same draft.
    • The tag stayed 2.3.0-beta.1.
    • The body and main.js were the last run's, and exactly one release existed for the tag.
    • The scratch draft was deleted afterwards.
  • A crafted tag like the one in the review matches nothing.
  • The listing finds the current 2.3.0 draft, which releases/tags/2.3.0 404s on.
  • The real test is the next step of the 2.3.0 release: re-pointing the tag after the notes restructure must update draft 398002294, not add a second one.

AI assistance: Claude Code found this during the 2.3.0 release, when a re-pointed tag created a duplicate draft, and wrote the fix at Leo's request. The tag_name rename was found while testing the fix. Leo still needs to review the workflow change.

Checklist

  • bun run check, bun run format, bun run lint, and bun run test pass locally (no source changes; workflow YAML validated)
  • I tried the change in a real Obsidian vault (or explained above why that isn't applicable): not applicable, CI-only change
  • I read CONTRIBUTING.md, including the section on AI assistance
  • If this adds a provider, a bundled skill, a built-in tool, or changes manifest.json: n/a

GitHub's releases/tags/<tag> endpoint never returns drafts, so re-pointing
an unpublished tag took the "no release yet" path and created a second
2.3.0 draft instead of updating the first. The workflow now lists releases
to find the tag's release, fails if more than one exists, and updates a
draft's notes and assets by release id; `gh release upload <tag>` misses
drafts the same way, so assets are replaced through the uploads API.

Co-Authored-By: Claude <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[High risk] Changes how release artifacts are uploaded in CI.

The PR appears safe to merge; no outstanding blocking finding was established.

Summary

The workflow now finds drafts by listing releases, updates them by ID, and replaces assets through the uploads API. The latest changes make a failed listing stop the job and skip uploads from runs whose tag has since moved.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Tag push builds assets] --> B{Remote tag still points to build commit?}
  B -- No --> C[Skip upload]
  B -- Yes --> D[List releases for tag]
  D --> E{Matching releases}
  E -- None --> F[Create draft]
  E -- Multiple --> G[Fail]
  E -- One published --> G
  E -- One draft --> H[Update notes and replace assets by ID]
  H --> I{Still a draft?}
  I -- No --> G
  I -- Yes --> J[Complete]
Loading

Reviews (3) · Last reviewed commit: "fix(release): pipefail on the release lo..."

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
- Pass the tag (and asset names) to jq with --arg instead of splicing them
  into the filter text.
- Serialize runs per tag, queued rather than cancelled, so two pushes of
  the same tag can't both find no release and each create a draft.
- Paginate the asset lookup.
- Restate tag_name when PATCHing the draft's notes: a PATCH without it
  renames a draft's tag to untagged-<hash> (verified against a draft on an
  existing tag), which would detach the draft from its tag and send the
  next run down the create path. Likely also how 2.1.1 was published as
  untagged-<hash>.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
…build uploads

- set -o pipefail in the create/update step (run steps default to bash -e
  without it), so a failed release listing fails the step instead of
  reading as "no release" and creating a duplicate draft.
- Before touching the release, check that the tag still points at the
  commit this run built; if it was re-pointed since, exit without
  uploading. GitHub keeps only the newest pending run per concurrency
  group and doesn't promise push order, so this, not the queue, is what
  keeps an older build off the draft.

Co-Authored-By: Claude <noreply@anthropic.com>
@Leo310
Leo310 merged commit bebc54d into main Sep 28, 2026
3 checks passed
@Leo310
Leo310 deleted the fix/release-draft-lookup branch September 28, 2026 07:01
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