Skip to content

reflex-release: lift unshippable dependency pins when materializing a release - #6889

Merged
masenf merged 4 commits into
mainfrom
claude/reflex-release-dev-pin-upgrades-hluw0c
Aug 27, 2026
Merged

reflex-release: lift unshippable dependency pins when materializing a release#6889
masenf merged 4 commits into
mainfrom
claude/reflex-release-dev-pin-upgrades-hluw0c

Conversation

@masenf

@masenf masenf commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

The problem

A package that depends on a sibling it is waiting for pins the unreleased version β€” widget-core >= 0.2.0.dev1 β€” so the workspace resolves while the sibling is still unpublished. That pin cannot be published: *.dev versions never reach PyPI, so the metadata would be uninstallable. check-dev-pins rejects it at build time, which means someone has to remember to lift it once the sibling is out β€” and finds out at the publish gate, with the changelog already bumped, when they forget.

What this does

Materialization lifts the pin. Every dependency floor the release cannot ship is rewritten to the earliest published version that satisfies the whole requirement, uv.lock is re-resolved, and both land in the release commit alongside the changelog bump β€” through the same review.

Floor Materializing a prerelease Materializing a final version
>= 0.2.0.dev1 earliest published 0.2.0a1, 0.2.0, … earliest published final 0.2.0, …
>= 0.2.0a1 left alone β€” an alpha may ship it lifted to the earliest published final
>= 0.2.0 left alone left alone

A floor no published version satisfies has nowhere to go, so the package is held back at plan time rather than materialized into a version that could never be published: auto-selected packages are dropped from the batch (a lockstep group whole, since its members only release together) and listed in the run summary, while an explicit selection fails the dispatch. Release the depended-on package first and the next release lifts the pin by itself.

Design notes worth reviewing

  1. "Published" means tagged. Resolution reads the repository's own git tags, not PyPI β€” tags are created only after a successful upload, which is already this pipeline's record of what shipped. Offline, deterministic, no new network dependency in the plan step; the cost is that it only works for siblings, and that the release workflows must check out full history and tags (they do).
  2. Dependencies outside the repository. A *.dev floor on one holds the release back (it is unpublishable regardless, and check-dev-pins would fail the build anyway); a prerelease floor on one is left alone, since its releases are not recorded here and pinning a beta is somebody's deliberate choice.
  3. Lifting prerelease floors on a final release is beyond the literal problem, and closes it. Without it the feature leaks: an alpha release rewrites >= 0.2.0.dev1 to >= 0.2.0a1, check-dev-pins is satisfied, and the subsequent final release ships a floor that resolves users onto an alpha. Happy to scope this back to dev floors only if you would rather.
  4. uv lock is the lock refresh, auto-detected from a root uv.lock and skipped when there is none. Not configurable β€” the tool already assumes uv everywhere (cli-command, setup-uv in every generated job).
  5. Lockstep siblings under pin-exact are skipped, since pin-lockstep rewrites those to == <version> at build time and nothing declared here is shipped.
  6. The upgrade is atomic and narrowly staged. Pin rewrites and the lock refresh succeed together or are rolled back, so a re-run never finds the pins already lifted and skips re-locking. materialize reports the paths it rewrote as a repinned output β€” it runs as a separate process from the delivery step β€” and _commit_changelogs (now _commit_materialized) stages exactly those beside the changelogs, so unrelated worktree edits stay out of the release commit.

dispatch_release.yml changes (the materialize step gains an id, the two delivery steps a REPINNED_JSON env), so adopting repos need a reflex-release sync; the PR drift check enforces it.

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

uv run pytest tests/units/reflex_release β€” 299 passed (34 new here): bound resolution across both prerelease modes, the whole-specifier-set constraint, requirement rewriting that preserves extras/markers/other specifiers and relaxes a strict floor, TOML basic- and literal-string rewriting, the lockstep and outside-dependency exemptions, rollback on a failed lock and on a mid-batch failure, and the plan-time hold-back in explicit, auto and lockstep-group forms. ruff check, ruff format --check and pyright clean.

Rebased onto main now that #6888 has merged; the two features compose cleanly (verified the rendered workflows carry both the post-release dispatch step and the repinned threading).

@masenf
masenf requested a review from a team as a code owner August 14, 2026 16:39
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Materialization now replaces unshippable sibling dependency floors with the earliest suitable tagged release and refreshes the lock file atomically.

  • Holds back packages when no published version can satisfy a dependency requirement.
  • Threads rewritten paths into release delivery so only intended materialized files are committed.
  • Updates generated workflow wiring, documentation, and unit coverage for pin resolution and rollback behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/reflex-release/src/reflex_release/devpins.py Adds dependency-bound discovery, tag-based replacement selection, requirement-preserving TOML rewrites, lock refresh, and rollback handling.
packages/reflex-release/src/reflex_release/commands.py Integrates blocked-pin filtering into planning and stages repinned files during materialized release delivery.
packages/reflex-release/src/reflex_release/cli.py Accepts and forwards materialization’s repinned-path output to release delivery commands.
packages/reflex-release/src/reflex_release/templates/workflows/dispatch_release.yml Threads the materialize step’s repinned output into both release delivery paths.
tests/units/reflex_release/test_devpins.py Covers dependency resolution, requirement rewriting, lock handling, exemptions, and transactional rollback.
tests/units/reflex_release/test_commands.py Covers plan-time holdbacks and committing materialized dependency changes.

Reviews (5): Last reviewed commit: "Scope the pin rewrite to published requi..." | Re-trigger Greptile

Comment thread packages/reflex-release/src/reflex_release/devpins.py
@codspeed-hq

codspeed-hq Bot commented Aug 14, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

βœ… 27 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/reflex-release-dev-pin-upgrades-hluw0c (15e2f8d) with main (d3a17c7)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-release/src/reflex_release/devpins.py Outdated
Comment thread packages/reflex-release/src/reflex_release/devpins.py
Comment thread packages/reflex-release/src/reflex_release/devpins.py Outdated
Comment thread packages/reflex-release/src/reflex_release/commands.py Outdated
Comment thread packages/reflex-release/src/reflex_release/devpins.py

masenf commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

All four outstanding points addressed in 75e98b5. (The two shallow-clone threads @masenf already closed as by-design β€” agreed, and the README now says so where it explains that "published" means tagged.)

Strict floor rewritten wrong (P1) β€” a real bug, good catch. widget-core > 0.2.0.dev1 admits 0.2.0, so 0.2.0 is what it resolves to, and rewriting the operator verbatim produced > 0.2.0 β€” excluding the very release the requirement had just been lifted onto. Under an upper bound (> 0.2.0.dev1, < 0.3 with only 0.2.0 in range) it becomes unsatisfiable outright. A strict floor over an unreleased version now becomes an inclusive floor over the release above it.

I also added the invariant behind it: rewritten() now checks the result against the version it resolved to and fails rather than returning it. Reverting just the operator mapping, that check fires on its own β€” lifting 'widget-core > 0.2.0.dev1' produced 'widget-core > 0.2.0', which 0.2.0 does not satisfy β€” so a future gap in the operator handling can't publish metadata that resolves to nothing.

TOML escaping (P2) β€” real, and fail-safe rather than corrupting, but it aborts a release for a legal input. Confirmed the mechanism: for "widget-core >= 0.2.0.dev1; python_version > \"3.10\"", tomllib hands back the unescaped value, so the quoted search found 0 occurrences. Both spellings a value can have are now tried β€” an escaped basic string, and a literal string, which cannot escape anything. Not worth a round-tripping TOML dependency on the release path for this.

Non-atomic upgrade (P2) β€” real, though it needs a hand-run to bite. In CI a failed materialize fails the job on a throwaway checkout, so nothing is committed; locally the tree keeps lifted pins beside the old lock, and a re-run finds nothing left to lift, skips the refresh, and could commit exactly that pairing. The pyproject rewrites are now rolled back when uv lock cannot follow them, so a re-run has the same work to do.

Over-broad staging (P2) β€” you're right that the comment overclaimed. The obstacle was that materialize and the delivery step are separate processes, so the committing side could not know what had been rewritten. Fixed by making it known: materialize now emits a repinned output listing the paths, threaded to open-release-pr/push-prerelease the way releases already flows from plan. Only those paths are staged, so an unrelated uncommitted edit to a released package's pyproject.toml stays out of the release commit β€” there's a test for exactly that.

279 passing (6 new); ruff check, ruff format --check and pyright clean. Note dispatch_release.yml changed (the materialize step gained an id and the two delivery steps a REPINNED_JSON env), so this needs a reflex-release sync in any adopting repo β€” the PR drift check enforces it.


Generated by Claude Code

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 7 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-release/src/reflex_release/devpins.py
Comment thread packages/reflex-release/src/reflex_release/devpins.py
@masenf
masenf force-pushed the claude/reflex-release-dev-pin-upgrades-hluw0c branch from fc0dc7b to f113690 Compare August 15, 2026 00:29

@FarhanAliRaza FarhanAliRaza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ran this for real rather than reading it: built a sandbox repo with a root package, a sibling, and tags at 0.1.0 / 0.2.0a1 / 0.2.0 / 0.3.0, then drove plan -> materialize -> push-prerelease through a dozen shapes.

The behaviour matches the README table. >= 0.2.0.dev1 lifts to >= 0.2.0 for a final and >= 0.2.0a1 for a prerelease; > 0.2.0.dev1 becomes >= 0.2.0; extras and a python_version > "3.10" marker survive untouched; a prerelease floor on an outside dep is left alone while a dev floor on one is held back; an unsatisfiable floor drops an auto-selected package (with a ### Held back summary) and fails an explicit one; the two-package rollback from the last commit really does restore both pyprojects and leave the fragments on disk; and the commit carries exactly the changelog, the fragment deletion and pyproject.toml, with an unrelated dirty file left out.

Two things I'd fix before merging, plus a few caveats β€” inline.

Two more that didn't have a good line to hang on:

  • The pin's target version and the release action can disagree. >= 0.4.0.dev1 on a sibling at 0.3.0 plus release-patch produces 0.3.1, which still doesn't satisfy the floor, so the dependent stays held back until someone picks a minor. Worth a line in the README next to "the next release lifts the pin by itself".
  • The rollback guard covers apply + lock but not the towncrier step after it. I made towncrier fail and pyproject.toml was left lifted. CI throws the worktree away so this only bites local runs β€” noting it rather than asking for a change.

Tests, ruff and pyright are clean (299 passed).

Comment thread packages/reflex-release/src/reflex_release/devpins.py Outdated
Comment thread packages/reflex-release/src/reflex_release/commands.py
Comment thread packages/reflex-release/src/reflex_release/devpins.py
Comment thread packages/reflex-release/src/reflex_release/devpins.py Outdated
claude added 4 commits August 25, 2026 21:24
A package that depends on a sibling it is waiting for pins the unreleased
version β€” `widget-core >= 0.2.0.dev1` β€” so the workspace resolves while the
sibling is still unpublished. That pin cannot be published, and `check-dev-pins`
rejects it at build time, which leaves someone to remember to lift it by hand
once the sibling is out.

Materialization does it instead. Every floor the release cannot ship β€” a `*.dev`
floor, and a prerelease floor on a sibling when the version being materialized
is final β€” is rewritten to the earliest published version that satisfies the
whole requirement, `uv.lock` is re-resolved, and both land in the release commit
alongside the changelog bump, through the same review.

"Published" means tagged: tags are created only after a successful upload, so
the repository's own tags are its record of what is on PyPI. A published
prerelease therefore satisfies a floor only when the release being materialized
is itself a prerelease; a final version never floors its users on a sibling's
alpha.

A floor no published version satisfies has nowhere to go, so the package is held
back at plan time rather than materialized into a version that could never be
published: auto-selected packages are dropped from the batch (a lockstep group
whole, since its members only release together) and listed in the run summary,
while an explicit selection fails the dispatch.

Two things are left alone: a floor on a lockstep sibling that `pin-exact`
rewrites at build time anyway, and a prerelease floor on a dependency outside the
repository, whose releases are not recorded here. A `*.dev` floor on an outside
dependency still holds the package back β€” that pin is unpublishable whoever owns
it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B13GVXzLdYzmqmMnaKWvyV
Fix a real rewrite bug. `widget-core > 0.2.0.dev1` admits 0.2.0, so 0.2.0 can be
what it resolves to β€” but rewriting the operator verbatim produced
`> 0.2.0`, excluding the very release the requirement had just been lifted onto,
and under an upper bound that could leave it unsatisfiable. A strict floor over
an unreleased version now becomes an inclusive floor over the release above it,
and every rewrite is checked against the version it resolved to before being
written, so a future gap in the operator handling fails loudly instead of
publishing metadata that resolves to nothing.

Handle TOML escaping. A requirement carrying a double-quoted marker is escaped in
the file but comes back from the parser unescaped, so searching for the parsed
value found nothing and aborted materialization. Both spellings a value can have
β€” an escaped basic string and a literal string, which cannot escape anything β€”
are now tried.

Make the upgrade atomic. If `uv lock` failed, the rewritten pyproject files were
already on disk beside the old lock file, and a re-run would find nothing left to
lift, skip the lock refresh, and could commit exactly that pairing. The rewrites
are rolled back when the lock cannot follow them.

Stage only what materialization wrote. `_commit_materialized` promised as much
but built its list from every released package's pyproject plus the lock file
whenever one existed, so an unrelated uncommitted edit to one of those tracked
files would ride along in the release commit. `materialize` now reports the paths
it rewrote as a `repinned` output β€” it runs as a separate process from the
delivery step, which cannot otherwise know β€” and only those are staged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B13GVXzLdYzmqmMnaKWvyV
The rollback guard started one line too late. `apply_pin_upgrades` rewrites
package by package, so a requirement the second package cannot be given β€” one
that appears twice, or a rewrite its resolved version would not satisfy β€” left
the first package's pins lifted in the worktree while the command failed.

Bring the rewrite itself inside the guard, so the whole batch and the lock file
move together or not at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B13GVXzLdYzmqmMnaKWvyV
…ifted pins

Four things the review turned up.

The rewrite counted occurrences across the whole file, so a copy of the same
requirement in `[dependency-groups]` β€” development-only, and deliberately
ignored by `check_dev_pins` β€” made the count ambiguous and aborted an otherwise
fine release. It now counts and rewrites only inside the `[project]` table and
its subtables, which is exactly what `published_dependencies` reads. The same
string in `dependencies` and in an optional-dependency group is two published
requirements rather than an ambiguity, and both are lifted.

A lifted pin could fail to reach the release commit. When the delivery step
passes no `repinned` paths β€” a consumer whose workflow predates that output, and
`cli-command` may be unpinned, so the tool can gain the output before the
workflow does β€” the commit went out with the old pin and died at the
publish-time gate, which is the failure this feature exists to remove, arriving
later and with more to unwind. The commit now fails if a file materialization
owns is still modified after staging, naming the re-sync.

An exact floor (`== 0.2.0.dev1`) was told to release the dependency first and
wait, which is a circle: no published version can ever equal an unpublished one.
It is now reported as a dead end that has to be re-pinned by hand, and the
closing advice is derived from what the blockers actually are.

The lock file is reported only when the re-resolution moves it. In a uv
workspace it never does for a sibling pin β€” the lock records members as
`editable` with no specifier β€” and staging it regardless would have carried
unrelated resolution churn into a release commit. The README now says that
plainly instead of claiming nothing else is staged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B13GVXzLdYzmqmMnaKWvyV
@masenf
masenf force-pushed the claude/reflex-release-dev-pin-upgrades-hluw0c branch from f113690 to 15e2f8d Compare August 25, 2026 21:35
@masenf
masenf merged commit c5e0a75 into main Aug 27, 2026
134 of 135 checks passed
@masenf
masenf deleted the claude/reflex-release-dev-pin-upgrades-hluw0c branch August 27, 2026 20:01
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>
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.

3 participants