Skip to content

release, nightly: publish the staged release by id, refuse duplicate drafts, resume a complete staged release - #21139

Merged
lawrencecchen merged 3 commits into
feat-cmux-nextfrom
feat-release-dup-draft-guard
Oct 16, 2026
Merged

lawrencecchen merged 3 commits into
feat-cmux-nextfrom
feat-release-dup-draft-guard

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR ports the v0.65.2 duplicate-draft fix (#21137, which is still open on main) to the feat-cmux-next universal-app release.yml. It also fixes the two follow-ups: beads cx-sqadk (the guard cannot see drafts) and cx-9ol90 (nightly uses a softprops tag lookup).

Incident: v0.65.2 was published with 0 assets. publish-release-assets.py staged draft 414419116 and uploaded 12 assets to it. Then softprops/action-gh-release looked the release up by tag. That lookup returns 404 for drafts, so the action created release 414419301 and published it with 0 assets (job log of run 38621170537, attempt 5).

Changes

  • scripts/ci/publish-release-assets.py: before any upload, the script refuses when the tag has more than one release object, draft or published. The new --github-output flag reports release_id, asset_count and asset_names.
  • New scripts/ci/finalize-release.py:
    • publish publishes the given release id. It refuses when any other release exists for the tag. It clears prerelease and sets the metadata first, then sets draft=false and make_latest. It then checks that the tag resolves to the same id, that the release is not a draft, and that the expected assets are there (exact count when one is given).
    • download takes assets from a staged draft and checks the digest.
  • release.yml:
    • The guard now lists drafts too, and it refuses duplicate release objects (cx-sqadk).
    • When the existing release (draft or published) already has every immutable asset, the run does not rebuild, because a rebuild makes new signed bytes. It resumes instead: it downloads the release's own appcast.xml, publishes the release by id and uploads the appcast to R2. Before, a failure between upload and publish could not be fixed by a rerun, and a missed R2 upload stayed missed.
    • Publish verified release now uses finalize-release.py with the upload step's id and asset count. softprops is removed.
  • nightly.yml: Publish verified nightly release metadata and Record verified nightly publication now publish the staged channel release by id with the same release body and token, in place of the softprops tag lookup (cx-9ol90).
  • tests/test_github_release_publication.py: the existing workflow-text assertions are changed to match the new steps. One existing case is changed to have a single draft, because two drafts for one tag are now refused. No new tests (behavior-test rule).

Changelog

none

Verification

  • actionlint passes on release.yml. On nightly.yml it reports only a pre-existing SC2129 at line 1597, which is not in this diff.
  • finalize-release.py publish/download --help load correctly, and py_compile passes.
  • Per the no-tests-on-laptop rule, the Python tests were not run locally. The CI ci-guards and r2-upload-tests jobs run them.
  • The real release and nightly paths run only on a tag push or the nightly schedule, so this PR does not exercise them.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes release and nightly publication so the staged release is published by id, preventing releases from being published with 0 assets (v0.65.2 shipped empty this way).

  • The upload script refuses when more than one release object exists for the tag and reports the staged release id, asset count, and asset names.
  • New finalize-release.py publishes a release by id, refuses other releases for the same tag, and verifies the published release holds the expected assets; its download command fetches staged assets by id without sending the token on GitHub's redirect.
  • release.yml: the guard lists drafts, refuses duplicate release objects, and skips the build when a complete staged draft exists; the rerun then resumes publication by id with the exact asset count the guard saw, without rebuilding, so signed bytes stay intact. A rerun of an already-published release is a no-op.
  • nightly.yml publishes the staged release by id in place of the softprops tag lookup.
  • CI now proves finalize-release.py loads.

Written for commit d4432e6. Summary will update on new commits.

View guided diff Turn on auto-fix

… staged release

v0.65.2 was published with 0 assets: softprops/action-gh-release looked the
release up by tag, which 404s for drafts, created a second release and
published it while the 12 assets sat on the staged draft.

- publish-release-assets.py refuses before upload when more than one release
  object exists for the tag, and reports release_id, asset_count, asset_names.
- New scripts/ci/finalize-release.py publishes a release by id, refuses any
  other release for the tag, and verifies the published release by tag holds
  the expected assets. It can also download assets from a staged draft.
- release.yml: the guard also lists drafts, refuses duplicates, and when the
  staged release already holds every immutable asset it skips the build and
  resumes: download the staged appcast, publish by id, upload R2.
- nightly.yml: both release metadata steps publish by id with the script
  instead of softprops tag lookup.
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: f0d666e7-86ac-48d9-8c95-79fa3f4dc635

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Correctness review (subagent) and resolution

Result: no blockers. Commit 2 on this branch fixes the findings below where noted.

# Severity Finding Resolution
1 should-fix No CI job runs finalize-release.py, so a script defect would first show at real publish time. Fixed. r2-upload-tests.yml now triggers on the script and runs finalize-release.py publish --help and download --help, which proves that the module and its publish-release-assets.py import load. I did not add a fake-server unit test, because of the team rule of behavior tests only.
2 should-fix origin/main still has the bug. A separate PR against main is coming. It needs Lawrence's approval.
3 nit download sent the bearer token on to the asset CDN redirect. Fixed. Redirects are turned off, and the script fetches the Location URL without the token.
4 nit make_latest is sent in the same PATCH as draft=false. Kept. gh release edit --draft=false --latest does the same. The first stable release after this merge is the real test.
5 nit The resume path has no exact count. Kept. The resume path requires every asset already on the release plus the MDM files, and the stable feed uses no replace-temp names.
6 nit Unquoted $UPLOADED_ASSETS loops. Fixed. The lists are now read with read -ra into arrays.

The reviewer also checked these and found them correct:

  • the guard cases: none, partial, complete draft and complete published
  • the step conditions on the resume path
  • the PATCH semantics for nightly (make_latest:false on a prerelease, and target_commitish)
  • the old nightly body against the new one, line by line
  • the nightly-next token expression
  • the publish_assets outputs on both the own-build and the recovered-build paths
  • the release id across the source-archive step
  • the existing text tests
  • that no secret is exposed

@cursor

cursor Bot commented Oct 16, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

Review follow-up: a rerun of a published release stays a no-op so a demoted
release is never republished; the resumed draft must have exactly the asset
count the guard saw, all uploaded.
@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Addendum. The review of the main port (#21140) found 1 should-fix and 2 nits that also apply to this branch. Commit d4432e6b8f63f fixes them here too:

  • Only a complete staged draft resumes. A rerun of a published release stays a no-op, so a demoted release is never published again.
  • The resume path checks the exact asset count that the guard saw.
  • The script sets names=() before read -ra.

@lawrencecchen
lawrencecchen merged commit fad7929 into feat-cmux-next Oct 16, 2026
74 checks passed
@lawrencecchen
lawrencecchen deleted the feat-release-dup-draft-guard branch October 16, 2026 05:12
lawrencecchen added a commit that referenced this pull request Oct 16, 2026
…covered build (#21144)

* nightly: run publication tooling from the workflow commit, not the recovered build

publish-nightly checks out the published build's commit. A recovered build can
be days older than the workflow: run 38855116561 recovered 0664bd9, whose
nightly_version.py predates --already-published-ok (#20986), so the recheck
failed with unrecognized arguments. The same skew would break
publish-release-assets.py --github-output and finalize-release.py (#21139).

Check out scripts/ at github.sha into .publish-tooling and run the publication
tooling from there: nightly_version, nightly_release_notes, nightly-next-guard,
prune, publish-release-assets, finalize-release, upload-r2-object and
update-release-tag. Scripts that read the build's own source tree stay on the
publish ref.

* nightly: pin every moved publication script to the tooling checkout in the test

Review follow-up for #21144.

* test: accept the publication tooling path for the release tag helper

* test: allow the publication tooling checkout at github.sha in publish-nightly
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