Fix release workflow skipping publish when using custom builds - #6950
Merged
Conversation
Exactly one of the built-in `build` job and a `[[tool.reflex-release.custom-build]]` job runs for a given package, so the other is always skipped. GitHub evaluates the implicit `success()` a job gets when its `if` names no status function over the whole transitive dependency closure rather than the direct `needs`, so that skipped build reached `publish` straight through the `collect` written to absorb it, and `tag-and-release` behind it: every build succeeded, the artifacts were verified and checksummed, and the upload silently never happened. Guard both jobs with an explicit `needs.<job>.result == 'success'`. Not `!failure()` as `collect` uses β a skipped `publish` is neither failed nor cancelled, so tolerating it would push the tag for a release that uploaded nothing. `release_from_changelog.yml`'s `report` job, the canonical failure signal for a partial release, was blind to the same shape: it accepted any skipped leg, so a batch that published nothing exited 0, and its "held back" diagnostic sat behind a flag that case never set. It now accepts a skipped leg only when detection found nothing for that leg to publish. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011bhWhmMrSDwsuUK41vXQ4T
Merging this PR will not alter performance
Comparing Footnotes
|
Contributor
Greptile SummaryThe PR prevents skipped build paths from suppressing package publication and tightens reporting when a release leg with detected work does not run.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/reflex-release/src/reflex_release/templates/workflows/publish.yml | Adds explicit dependency-result guards so skipped transitive build paths do not suppress publishing or allow tagging without a successful upload. |
| packages/reflex-release/src/reflex_release/templates/workflows/release_from_changelog.yml | Makes the aggregate report distinguish legitimate no-work skips from skipped release legs that had packages to publish. |
| tests/units/reflex_release/test_scaffold.py | Adds focused regression coverage for publish/tag conditions and report-step result validation. |
| packages/reflex-release/news/+publish-skip-propagation.bugfix.md | Documents the custom-build skip-propagation bug and the corrected release behavior. |
Reviews (2): Last reviewed commit: "test(reflex-release): run the report ste..." | Re-trigger Greptile
`bash` on PATH on a Windows runner is the WSL launcher, which exits 1 with "Windows Subsystem for Linux has no installed distributions" β so the report cases expecting a red exit passed for the wrong reason and the green ones failed. Resolve Git for Windows' bash there instead (it ships beside the git the runners already have) and skip when no POSIX bash exists at all, so the worst case is a skip rather than a false failure. Inherit the environment rather than building a bare one, which bash on Windows needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011bhWhmMrSDwsuUK41vXQ4T
FarhanAliRaza
approved these changes
Aug 26, 2026
masenf
added a commit
that referenced
this pull request
Aug 26, 2026
* fix(reflex-release): stop a skipped build path from skipping the upload Exactly one of the built-in `build` job and a `[[tool.reflex-release.custom-build]]` job runs for a given package, so the other is always skipped. GitHub evaluates the implicit `success()` a job gets when its `if` names no status function over the whole transitive dependency closure rather than the direct `needs`, so that skipped build reached `publish` straight through the `collect` written to absorb it, and `tag-and-release` behind it: every build succeeded, the artifacts were verified and checksummed, and the upload silently never happened. Guard both jobs with an explicit `needs.<job>.result == 'success'`. Not `!failure()` as `collect` uses β a skipped `publish` is neither failed nor cancelled, so tolerating it would push the tag for a release that uploaded nothing. `release_from_changelog.yml`'s `report` job, the canonical failure signal for a partial release, was blind to the same shape: it accepted any skipped leg, so a batch that published nothing exited 0, and its "held back" diagnostic sat behind a flag that case never set. It now accepts a skipped leg only when detection found nothing for that leg to publish. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011bhWhmMrSDwsuUK41vXQ4T * test(reflex-release): run the report step with a POSIX bash `bash` on PATH on a Windows runner is the WSL launcher, which exits 1 with "Windows Subsystem for Linux has no installed distributions" β so the report cases expecting a red exit passed for the wrong reason and the green ones failed. Resolve Git for Windows' bash there instead (it ships beside the git the runners already have) and skip when no POSIX bash exists at all, so the worst case is a skip rather than a false failure. Inherit the environment rather than building a bare one, which bash on Windows needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011bhWhmMrSDwsuUK41vXQ4T --------- Co-authored-by: Claude <noreply@anthropic.com>
masenf
pushed a commit
that referenced
this pull request
Aug 27, 2026
Merge brings in two reflex-release changes that alter the templates: dependency-pin lifting during materialize (#6889) and the skip-propagation fix in publish.yml and release_from_changelog.yml (#6950). `sync --check` flagged all three affected workflows as drifted, which is the mechanism working β a template change on main that never reached this repository's own workflows is exactly what it is there to catch. Regenerated; the uv and Python pins and the dispatch checkboxes are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXbekKtPyfbENVnZdTRxKX
masenf
added a commit
that referenced
this pull request
Aug 27, 2026
β¦package (#6941) * Replace the release workflows with the bundled reflex-release The publish pipeline was a hand-maintained set of workflows over scripts/release.py and .github/scripts/*. reflex-release, which lives in this repository at packages/reflex-release, is that same pipeline packaged and generalized, so the workflows now come from its templates instead. Every reflex-specific behavior moves into [tool.reflex-release] in the repo-root pyproject.toml: the reflex/reflex-base lockstep pair (one version, exact pin rewritten at build time, reflex uploaded last), the internal packages that patch-release on every push, the America/Los_Angeles release timezone, and the packages exempt from the news-fragment check. The .pyi check on the reflex wheel becomes the pipeline's post_build.sh hook, and the dev-pin gate is now reflex-release's own check-dev-pins, which reports identically to scripts/check_min_deps.py --check-dev-pins. cli-command runs the copy in this repository straight from uv.lock rather than a version published to PyPI: uv run --frozen --package reflex-release reflex-release That pins the pipeline to one commit. The workflows are rendered from the templates of the commit that contains them, so `sync --check` β which changelog.yml now runs on every pull request β fails both on a workflow edited by hand and on a template change that was never regenerated. The two can no longer drift apart. What changes in behavior, beyond the move: - publish.yml splits validation, build and verification into separate unprivileged jobs and puts the SHA-256 manifest in front of the approver; the manifest is also attached to the GitHub release. - changelog.yml also runs on pull requests targeting the publishing branches, and its release-branch exemption for version headings now requires the pull request to be authored by github-actions[bot]. - Dispatch release takes a comma-separated package list instead of one checkbox per package, which no longer fits GitHub's workflow_dispatch input limit. - auto_release_internal.yml triggers on an internal package's src/ rather than its whole directory, matching what detection counts as its source, and diffs the whole pushed range instead of the last commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXbekKtPyfbENVnZdTRxKX * Pin the release toolchain in reflex-release, restore the checkboxes Four changes to the tool, and the regenerated workflows that follow from them. Pinned toolchain. New uv-version and python-version keys write the uv and Python the generated workflows install verbatim into every setup-uv step, in place of whatever that action resolves at run time. Both default to a version reflex-release itself pins, so upgrading the tool moves the release toolchain with it and `sync --check` reports that as drift until the workflows are regenerated β the same signal a template change already gives. A repository that wants its own cadence sets either key; "" leaves that version to the setup action. Both are interpolated into a quoted YAML scalar, so they are validated against a version-or-specifier pattern rather than trusted. The pins live in one rendered block instead of in each template, and a test asserts every setup-uv step in every template carries the placeholder that receives it: render() only fails on a placeholder it cannot substitute, so a step added without one would otherwise silently install an unpinned uv. Pinned build backend. reflex-release pins hatchling and uv-dynamic-versioning exactly. Build requirements are resolved fresh rather than locked, so a repository that vendors the tool β as this one now does, running it out of uv.lock β no longer has the backend that builds its release tooling move underneath it. Checkbox limit. Ten was wrong: workflow_dispatch takes twenty inputs, and this repository's own dispatch form has been running eighteen checkboxes. Raised to nineteen packages plus the release action, which brings the checkboxes back here β the comma-separated fallback was a regression, not a fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXbekKtPyfbENVnZdTRxKX * Regenerate the release workflows after merging main Merge brings in two reflex-release changes that alter the templates: dependency-pin lifting during materialize (#6889) and the skip-propagation fix in publish.yml and release_from_changelog.yml (#6950). `sync --check` flagged all three affected workflows as drifted, which is the mechanism working β a template change on main that never reached this repository's own workflows is exactly what it is there to catch. Regenerated; the uv and Python pins and the dispatch checkboxes are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXbekKtPyfbENVnZdTRxKX * Add never-publish-packages; append new Config fields Both from cubic's review of #6941. never-publish-packages, for the packages a repository builds but never releases. changelog-exempt-packages was the closest thing, and it only waives the news-fragment requirement β it left integrations-docs with a release checkbox and publishable by hand, which the checkbox form made visible. A listed package now gets no checkbox, is never auto-selected, is skipped by changelog detection even when it has a CHANGELOG.md, needs no fragment, and is refused by prepare-publish, so the one remaining way to reach it β typing it into the publish workflow β fails in the first unprivileged job rather than at verify-dist after a build. Being unreleasable it cannot also be a lockstep member, a custom-build package, latest-release-package or internal; each is rejected when the configuration loads. integrations-docs moves to the new key, which drops it from the Dispatch release form. Config's new fields move to the end of the dataclass. It is exported, so the generated __init__ has a positional contract: uv_version and python_version sat after cli_command, shifting every later argument for a caller that does not pass everything by keyword. A test pins the historical field order as a prefix so the next field added lands in the right place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXbekKtPyfbENVnZdTRxKX * Re-run the changelog check when pull request labels change Ports #6916 into the reflex-release changelog template, so the fix applies to every repository the tool scaffolds rather than to this one's copy of a workflow that is now generated. skip-changelog and changelog-version-edit waive parts of the check, so a verdict is only valid for the label set it was computed under. Three ways that broke: applying a label after the last push started no run at all, removing one left the green run that label produced standing with the gate silently open, and re-running a failed run replayed the original event payload β where the label does not exist yet β so the check kept failing until someone pushed again. So: labeled/unlabeled join the trigger types, and the labels are read back from the API into a step output the two guarded steps test, in a step that runs before the checkout because it needs nothing but `gh`. The job itself stays ungated: a job skipped by `if` reports its check as skipped, which branch protection counts as passing, so a cheap no-op on an unrelated label would overwrite a real failure with a green. Regenerated reflex's own changelog.yml from the template. The jq program was checked against the label sets it has to distinguish, running the generated step itself against a stubbed `gh`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXbekKtPyfbENVnZdTRxKX --------- Co-authored-by: Claude <noreply@anthropic.com>
masenf
added a commit
that referenced
this pull request
Aug 28, 2026
* Materialize changelogs for reflex-release@0.1.0a1 (new-prerelease-minor) * Materialize changelogs for reflex-release@0.1.0a2 (continued-prerelease) * Materialize changelogs for reflex-release@0.1.0a3 (continued-prerelease) * Fix release workflow skipping publish when using custom builds (#6950) * fix(reflex-release): stop a skipped build path from skipping the upload Exactly one of the built-in `build` job and a `[[tool.reflex-release.custom-build]]` job runs for a given package, so the other is always skipped. GitHub evaluates the implicit `success()` a job gets when its `if` names no status function over the whole transitive dependency closure rather than the direct `needs`, so that skipped build reached `publish` straight through the `collect` written to absorb it, and `tag-and-release` behind it: every build succeeded, the artifacts were verified and checksummed, and the upload silently never happened. Guard both jobs with an explicit `needs.<job>.result == 'success'`. Not `!failure()` as `collect` uses β a skipped `publish` is neither failed nor cancelled, so tolerating it would push the tag for a release that uploaded nothing. `release_from_changelog.yml`'s `report` job, the canonical failure signal for a partial release, was blind to the same shape: it accepted any skipped leg, so a batch that published nothing exited 0, and its "held back" diagnostic sat behind a flag that case never set. It now accepts a skipped leg only when detection found nothing for that leg to publish. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011bhWhmMrSDwsuUK41vXQ4T * test(reflex-release): run the report step with a POSIX bash `bash` on PATH on a Windows runner is the WSL launcher, which exits 1 with "Windows Subsystem for Linux has no installed distributions" β so the report cases expecting a red exit passed for the wrong reason and the green ones failed. Resolve Git for Windows' bash there instead (it ships beside the git the runners already have) and skip when no POSIX bash exists at all, so the worst case is a skip rather than a false failure. Inherit the environment rather than building a bare one, which bash on Windows needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011bhWhmMrSDwsuUK41vXQ4T --------- Co-authored-by: Claude <noreply@anthropic.com> * Materialize changelogs for reflex-release@0.1.0a4 (continued-prerelease) * Materialize changelogs for reflex-release@0.1.0 (release-from-prerelease) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Masen Furer <m_github@0x26.net> Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Type of change
Description
Fixes a critical bug in the release workflow where using a
[[tool.reflex-release.custom-build]]entry would cause the publish step to silently skip without uploading packages, while still reporting success.Root cause: When a custom build is configured, exactly one of the built-in
buildjob and the custom-build job runs for any given package β the other is always skipped. GitHub's implicitsuccess()status function (applied when no explicitifcondition is set) evaluates over the entire transitive dependency closure rather than just direct dependencies. This caused the skipped build path to propagate through thecollectjob (which absorbs both paths) and reach thepublishjob, silently skipping it despite having packages to publish.Solution:
publish.yml: Added explicit
ifcondition to thepublishjob that checksneeds.collect.result == 'success'and!cancelled()alongside the existing skipped check. This prevents the implicit success() from propagating through skipped dependencies.publish.yml: Added explicit
ifcondition to thetag-and-releasejob checkingneeds.publish.result == 'success'and!cancelled(). Using!failure()would be insufficient since a skipped publish is neither failed nor cancelled.release_from_changelog.yml: Enhanced the report step's validation logic to distinguish between legitimately skipped legs (when detect found no packages for them) and improperly skipped legs (when detect found packages but the leg didn't run). The script now:
ANYandANY_LASToutputs from the detect job to indicate whether each leg had workcheck_leg()function that only accepts 'skipped' when there was no work to dotest_scaffold.py: Added comprehensive test coverage:
test_the_gate_survives_the_build_path_that_is_always_skipped: Verifies the publish job has proper status checkstest_the_tag_is_pushed_only_after_a_successful_upload: Verifies tag-and-release usessuccess()notfailure()test_the_report_is_red_when_a_leg_with_work_did_not_publish: Parametrized test validating all combinations of leg states and work indicatorsTesting
Checklist
https://claude.ai/code/session_011bhWhmMrSDwsuUK41vXQ4T