reflex-release: add a post-release workflow hook - #6888
Conversation
`post-release-workflow` in `[tool.reflex-release]` names a workflow of the consuming repository's own. `publish.yml` dispatches it once per published tag, after the upload, the tag and the GitHub release all exist — the hook for whatever has to follow a release: publishing docs, refreshing an image, notifying a downstream repository. The dispatch runs on the tag, so the workflow sees exactly the tree that was published, and it is handed `tag`, `package` and `version` as workflow_dispatch inputs. All three are required because GitHub rejects a dispatch carrying inputs the target workflow does not declare. The step and the `actions: write` grant it needs — in `publish.yml` and in both workflows that call it — are rendered only when the setting is present, so a repository that runs nothing after a release keeps the narrower permissions. Naming a workflow this tool generates is rejected by `sync`, since handing a published tag back to the release pipeline would either re-enter it or fail on inputs it does not declare. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B13GVXzLdYzmqmMnaKWvyV
Greptile SummaryThe PR adds an optional post-release workflow dispatch after publishing each tag.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/reflex-release/src/reflex_release/config.py | Adds optional post-release workflow configuration and shared dispatch-contract constants. |
| packages/reflex-release/src/reflex_release/commands.py | Implements validation and dispatch of the configured workflow on the published tag. |
| packages/reflex-release/src/reflex_release/scaffold.py | Conditionally renders the dispatch step and permissions and rejects generated workflow identifiers. |
| packages/reflex-release/src/reflex_release/templates/workflows/publish.yml | Adds placeholders for conditional actions permission and the final post-release step. |
| tests/units/reflex_release/test_scaffold.py | Covers conditional rendering, permission propagation, generated-workflow rejection, and dispatch-contract rendering. |
Reviews (3): Last reviewed commit: "Merge branch 'main' into claude/reflex-r..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
4 issues found across 12 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/units/reflex_release/test_scaffold.py">
<violation number="1" location="tests/units/reflex_release/test_scaffold.py:593">
P3: The `with_post_release` helper added here is byte-for-byte identical to the one added in tests/units/reflex_release/test_commands.py (lines 895-913). Both edit pyproject.toml to inject `post-release-workflow` and reload the config. Since both test modules share the same `repo` fixture, hoist this helper into tests/units/reflex_release/conftest.py so the two test files don't each maintain a copy of the same setup.</violation>
</file>
<file name="packages/reflex-release/src/reflex_release/scaffold.py">
<violation number="1" location="packages/reflex-release/src/reflex_release/scaffold.py:384">
P2: When `post-release-workflow` names `auto_release_internal.yml` after internal packages are removed, this guard accepts the generated workflow because it is no longer in `managed_workflows(config)`. `sync` may delete the file or leave no receiver, so the post-release step fails on every release. Reject all generated names, including `OPTIONAL_WORKFLOWS`.</violation>
<violation number="2" location="packages/reflex-release/src/reflex_release/scaffold.py:384">
P2: check_post_release_workflow only rejects post-release-workflow values that match a generated workflow's filename (via managed_workflows). Since `gh workflow run` also resolves workflows by their display name (the `name:` field in the YAML), a user could configure the display name of a generated workflow (e.g. "Publish to PyPI") and this guard would miss it, causing the dispatch to hit the generated workflow with incompatible inputs or re-enter the release pipeline. Consider also checking against the generated workflows' display names, not just filenames.</violation>
</file>
<file name="packages/reflex-release/src/reflex_release/commands.py">
<violation number="1" location="packages/reflex-release/src/reflex_release/commands.py:1003">
P3: `cmd_post_release` dispatches `tag`, `package`, and `version` straight into `gh workflow run` without validating any of them. The CLI defaults to the empty string when the `TAG`/`PACKAGE`/`VERSION` env vars are unset, so a manual/misconfigured invocation sends `--field package=` and `--field version=`. `gh workflow run` returns 0 once the dispatch is accepted, so the release run does not fail loudly and the downstream workflow receives empty facts — contradicting the failure-handling design. The sibling publish commands validate inputs (`cmd_prepare_publish` calls `require_known(package)` and validates the version). Validate that the three values are non-empty (and `require_known(package)`) before dispatching.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Close two holes in the guard that rejects naming a generated workflow as the post-release hook. `gh workflow run` resolves a workflow by file name *or* by display name, so "Publish to PyPI" walked straight past a filename-only check; and the guard consulted `managed_workflows`, which drops auto_release_internal.yml once a repository has no internal packages — exactly when `sync` deletes the file, leaving the dispatch with no receiver at all. The check now covers every workflow this tool can generate, under both names. Validate the dispatch payload. The CLI defaults TAG/PACKAGE/VERSION to the empty string when the environment variables are unset, and GitHub accepts a dispatch carrying empty inputs, so a misconfigured invocation went green having told the downstream workflow nothing. All three are now required, and the package is checked against the repository the way the sibling publish commands do. Name the hook's contract once. The config key and the three dispatch input names were repeated across config parsing, the dispatch payload, the scaffolded step's comment and its failure message; they now come from POST_RELEASE_WORKFLOW_KEY and POST_RELEASE_INPUTS in config.py, so adding an input cannot leave the generated workflow and the payload disagreeing. Hoist the identical `with_post_release` test helper out of test_commands.py and test_scaffold.py into conftest.py as `set_post_release_workflow`, alongside the `write_lockstep` helper it mirrors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B13GVXzLdYzmqmMnaKWvyV
|
All five review points were valid and are addressed in f1fc2a5. Guard bypasses (P1 display names, P2 Empty dispatch payload (P3) — real. Duplicated identifiers (P2) — taken in the production code: One deliberate exception: the tests still spell the key and the input names out literally. Duplicated test helper (P3) — hoisted to Tests are now 265 passing (19 new, including the four bypass names as a parametrized case and the three empty-input cases); Generated by Claude Code |
#6888 (post-release workflow hook) landed on main and touches the same generated workflow, so three conflicts needed more than a textual union: - POST_RELEASE_STEP read `needs.build.outputs.tag`/`version`. This branch splits the old build job into prepare/build/collect, and tag-and-release now needs [prepare, publish] — so the dispatch reads those facts from prepare, the job that computes them. Retargeted, and its test with it. - main's render() gained "a placeholder alone on a line is an optional block: an empty value removes the line". @@DEFAULT_BUILD_GUARD@@ sat at the end of the build job's `if`, so an empty value would have eaten the newline and folded `runs-on:` into the expression. The guard is now a line of its own, and the two optional job/step blocks let the template carry their blank separator instead of embedding a trailing newline. - check_custom_build_workflows rejected naming a generated workflow via managed_workflows(), which has the hole main documents for the same guard: it drops auto_release_internal.yml exactly when a repository has no internal packages. Switched to main's GENERATED_WORKFLOWS, and made the custom-build table label public so the two guards name it from one place. Verified all four combinations of the two features render, parse as YAML and round-trip through `sync --check`, with a test covering both at once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E1ujR3svpGxMwm5pBMrsXh
All Submissions:
Type of change
What this does
Adds
post-release-workflowto[tool.reflex-release]. It names a workflow of the consuming repository's own, andpublish.ymldispatches it once per published tag, after the upload, the tag and the GitHub release all exist — the hook for whatever has to follow a release: publishing docs, refreshing an image, notifying a downstream repository.The dispatch runs on the tag, so the workflow sees exactly the tree that was published, and it is handed the three facts about the release:
All three inputs are required because GitHub rejects a dispatch carrying inputs the target workflow does not declare.
Design notes
actions: writegrant it needs, inpublish.ymland in both workflows that call it (release_from_changelog.yml,auto_release_internal.yml). A repository that runs nothing after a release keepsactions: readand gets no dead step.render()gained a small general rule for this: a placeholder alone on a line is dropped with its line when its value is empty.sync. Handing a published tag back to the release pipeline would either re-enter it or fail on inputs it does not declare.reflex-release sync. The existing PR drift check catches a repository that forgets.New
reflex-release post-releasecommand, wired throughenv:like every other step.Changes To Core Features:
uv run pytest tests/units/reflex_release— 256 passed (10 new, covering config parsing, both rendering paths, the permission widening in all three workflows, thesyncguard, and the dispatch/failure behavior).ruff check,ruff format --checkandpyrightclean.Split out of a combined branch alongside #6889; the two features are independent, and whichever lands second will need a trivial rebase on
README.md,commands.pyandtest_commands.py.