Skip to content

fix(release): prevent electron-builder duplicate-draft race#105

Merged
fgilio merged 2 commits into
mainfrom
fix/release-duplicate-draft-race
Jun 17, 2026
Merged

fix(release): prevent electron-builder duplicate-draft race#105
fgilio merged 2 commits into
mainfrom
fix/release-duplicate-draft-race

Conversation

@fgilio

@fgilio fgilio commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Why

The v0.17.4 release build succeeded but produced a broken release: electron-builder created two draft releases for the tag and split the assets across them. One draft held rfa-0.17.4-arm64.zip; the other held latest-mac.yml, the .dmg, and the blockmaps. Publishing either alone would have 404'd every existing user's macOS auto-updater, because latest-mac.yml points at a .zip that lived on the other draft.

Root cause confirmed against the installed source (electron-publish/out/gitHubPublisher.js, getOrCreateRelease at lines 55–103): the publisher lists releases and only calls createRelease() when no draft matches the tag. When multiple artifacts finish near-simultaneously, several publishers clear that check before any draft exists and each creates its own draft — a classic check-then-create (TOCTOU) race.

What

  • Pre-create the draft before the build. With the draft already present, every publisher matches it and returns early (if (release.draft) return release) instead of racing to create one. Idempotent — skips creation if a release for the tag already exists, so re-runs are safe.
  • Verify release artifacts after the build. Fails the job unless exactly one release for the tag carries all five expected assets (latest-mac.yml, …-arm64.zip, …-arm64.zip.blockmap, …-arm64.dmg, …-arm64.dmg.blockmap). A split or dropped upload can no longer reach a human as a publishable draft.
  • Arch test (tests/Arch/ReleaseWorkflowTest.php) guards both steps — presence and ordering (pre-create before build, verify after) — against silent removal.
  • Docs updated in .github/CLAUDE.md.

Testing

  • composer test:lint — pass
  • composer test:types — 0 errors
  • Core suite — 1513 passed (incl. 2 new arch tests)
  • actionlint + YAML parse — clean

Note: this workflow change can only be exercised on the next real tag push (PR CI runs lint/types/tests, not the macOS release build). The v0.17.4 release itself was already manually repaired and published correctly.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Enhanced release automation to prevent duplicate release drafts and ensure all macOS build artifacts are included before publishing.
  • Tests

    • Added workflow validation tests to verify release process integrity.

electron-builder's GitHub publisher creates a draft release per artifact
when none exists yet (getOrCreateRelease lists releases and only calls
createRelease when no draft matches the tag). When several artifacts
(zip, dmg, their blockmaps, latest-mac.yml) finish near-simultaneously,
multiple publishers clear that check before any draft exists and each
creates its own draft. The assets then split across two incomplete
drafts; a release published from a split draft 404s the macOS updater
because latest-mac.yml points at a .zip attached to the other draft.

Pre-create the draft before the build so every publisher attaches to it
instead of racing to create one, and add a post-build verification step
that fails the job unless exactly one release carries all five expected
artifacts. Arch test guards both steps (presence + ordering) against
silent removal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fgilio, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 23 minutes and 4 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf583f02-0cec-40c8-be91-7a84305b739a

📥 Commits

Reviewing files that changed from the base of the PR and between 5231be2 and 5c00220.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • tests/Arch/ReleaseWorkflowTest.php
📝 Walkthrough

Walkthrough

The release workflow gains two new shell steps: one that pre-creates a GitHub Release draft before the macOS electron-builder publish job (eliminating the race where parallel publishers each attempt to create the draft), and one post-build step that asserts exactly one release exists for the tag with all five expected macOS assets. Two architecture tests and a documentation line are added to match.

Changes

Release Workflow Draft Pre-creation and Artifact Verification

Layer / File(s) Summary
Pre-create draft and verify artifacts steps
.github/workflows/release.yml
Adds a step before the build that calls gh release create --draft if no draft exists yet, and a post-build "Verify release artifacts" step that errors if the release count for the tag is not exactly 1 or if any of the five expected macOS assets (latest-mac.yml, arm64 zip/dmg and blockmaps) are absent.
Architecture tests and documentation
tests/Arch/ReleaseWorkflowTest.php, .github/CLAUDE.md
Introduces two PHP tests that parse release.yml and assert the pre-create step precedes the publish command and the verify step follows the build. Updates CLAUDE.md with a one-line description of both behaviors.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A draft is born before the build begins 🏗️
No two releases race to win,
Five assets checked, each one in place,
The updater fetches without disgrace.
One release, one truth — the workflow grins. 🎉

🚥 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 clearly and specifically identifies the main change: preventing a race condition in electron-builder that causes duplicate draft releases. It accurately reflects the core problem and solution described in the PR objectives.
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.

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


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 and usage tips.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Screenshots

Updated for 5c00220.

copy-paths-bulk-default copy-paths-bulk-default
copy-paths-bulk-menu-open copy-paths-bulk-menu-open
copy-paths-single-menu-open copy-paths-single-menu-open

github-actions Bot added a commit that referenced this pull request Jun 17, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5231be2e1e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated

@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: 3

🤖 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.yml:
- Line 104: Replace the direct interpolation of steps.version.outputs.value at
lines 104 and 134 in the release.yml workflow file with the shell-safe syntax
${GITHUB_REF_NAME#v}. This prevents shell metacharacter injection
vulnerabilities that could occur if a pushed tag contains special characters
like dollar signs or backticks. Update the --title flag and any other locations
using steps.version.outputs.value to use the GITHUB_REF_NAME variable stripped
of the 'v' prefix instead, which is evaluated safely within the shell
environment.
- Around line 136-161: Fix two issues in the release verification step. First,
add --slurp to the gh api command that counts releases (before the --paginate
flag) to safely handle pagination by collecting all pages before applying the jq
filter, preventing multi-line count values that break the arithmetic comparison.
Second, after collecting the assets list with gh release view, add a check that
verifies the exact count of assets equals 5 (the expected count: latest-mac.yml,
zip, zip.blockmap, dmg, dmg.blockmap). If the asset count is not exactly 5, log
an error showing both the asset count and the assets present, then exit with
code 1. This ensures no stale or unexpected assets slip through to production.

In `@tests/Arch/ReleaseWorkflowTest.php`:
- Around line 34-41: The current test in ReleaseWorkflowTest.php only verifies
that expected asset names are contained in the workflow output using multiple
toContain() assertions, but it does not test the failure case where unexpected
or stale assets are present. Add assertions that verify the workflow properly
rejects workflows with unexpected assets to ensure the exact-count verification
is properly guarded. This means adding test cases that confirm the release
workflow verification fails when there are assets beyond the expected five,
alongside the existing positive assertions that verify the expected asset names
appear.
🪄 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

Run ID: 5987a004-5d9b-419a-8ff8-253934d8bdcc

📥 Commits

Reviewing files that changed from the base of the PR and between 5b47df9 and 5231be2.

📒 Files selected for processing (3)
  • .github/CLAUDE.md
  • .github/workflows/release.yml
  • tests/Arch/ReleaseWorkflowTest.php

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread tests/Arch/ReleaseWorkflowTest.php Outdated
Address PR review findings on the release-workflow guard:

- Pagination: gh api --paginate applied --jq per page, so once releases
  span >1 page the count became multi-line and the -ne test errored out,
  silently bypassing the guard. Fetch with --paginate --slurp and run jq
  once over the combined document.
- Template injection: stop interpolating ${{ steps.version.outputs.value }}
  into run blocks (a tag like v1.2.3$(...) would execute before the shell
  parses it). Derive version="${tag#v}" in-shell instead. The env: usage in
  the build step is safe and stays.
- Exact-set check: verify the release carries exactly the five expected
  assets, failing on unexpected stale extras as well as missing ones.

Arch tests extended to guard the slurp, exact-set, and in-shell-version
behaviors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fgilio

fgilio commented Jun 17, 2026

Copy link
Copy Markdown
Owner Author

Addressed all review findings in 5c00220:

  • Pagination (Codex P2 + CodeRabbit)gh api --paginate applied --jq per page, so a multi-page release list would make count multi-line and bypass the guard. Now fetched with --paginate --slurp and counted with a single jq pass.
  • Template injection (CodeRabbit)run blocks no longer interpolate ${{ steps.version.outputs.value }}; they derive version="${tag#v}" in-shell. The env: usage in the build step is safe and unchanged.
  • Exact-set verification (CodeRabbit) — the verify step now requires exactly the five expected assets, failing on unexpected/stale extras as well as missing ones (comm-based diff).
  • Test coverage (CodeRabbit) — arch tests extended to guard --slurp, the Unexpected release assets path, and the in-shell version derivation.

Validated locally: actionlint clean, Core suite 1514 passing (incl. 3 arch tests), PHPStan 0 errors, and the verify logic simulated across happy-path / multi-page / missing / unexpected / split cases.

github-actions Bot added a commit that referenced this pull request Jun 17, 2026
@fgilio fgilio merged commit 7c7badb into main Jun 17, 2026
16 checks passed
@fgilio fgilio deleted the fix/release-duplicate-draft-race branch June 17, 2026 14:12
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