Skip to content

ci(pythonpublish): add workflow_dispatch trigger for republishing tags#1981

Open
bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
bearomorphism:fix/1790-publish-workflow-dispatch
Open

ci(pythonpublish): add workflow_dispatch trigger for republishing tags#1981
bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
bearomorphism:fix/1790-publish-workflow-dispatch

Conversation

@bearomorphism
Copy link
Copy Markdown
Collaborator

@bearomorphism bearomorphism commented May 9, 2026

Description

Closes #1790.

Why

The publish run for v4.11.1 failed at the time of the original tag push and the release is absent from PyPI — confirmed by the triage comment on #1790: "Confirmed via https://pypi.org/pypi/commitizen/json — 4.11.1 is not in the release history." GitHub disallows re-running workflow runs older than 30 days via the UI or API, so the original job cannot be retried.

The practical recovery options are: recreate the tag (which re-fires the GitHub release workflow and risks breaking downstream automation that watches tag events), use a separate ad-hoc workflow, or add a workflow_dispatch trigger to the existing publish workflow so a maintainer can invoke it manually for any tag. The third option is the least invasive — it touches only the trigger block and one checkout parameter — and it becomes a permanent escape hatch for any future failed publish run without requiring tag recreation or out-of-band tooling.

What changed

File Change
.github/workflows/pythonpublish.yml Add workflow_dispatch trigger with a required ref string input; update the actions/checkout step to use ${{ inputs.ref || github.ref_name }}

How it works

  • workflow_dispatch block (.github/workflows/pythonpublish.yml:4–7 on master, extended by the patch): a single required string input named ref is declared, so the Actions UI presents a labelled text field when "Run workflow" is clicked. Marking it required: true prevents an accidental no-argument dispatch that would fall through to an ambiguous default.
  • Checkout fallback expression: the checkout step's ref parameter changes from the hardcoded ${{ github.ref_name }} at .github/workflows/pythonpublish.yml:20 (master) to ${{ inputs.ref || github.ref_name }}. On push events the inputs context is empty, so the expression short-circuits to github.ref_name — the tag that triggered the push — producing identical behaviour to the previous value. On workflow_dispatch events inputs.ref is the maintainer-supplied tag string and that value is used directly.
  • Repository guard unchanged (.github/workflows/pythonpublish.yml:11, master): the if: ${{ github.repository == 'commitizen-tools/commitizen' }} condition on the deploy job applies to both trigger types, so forks cannot accidentally publish by dispatching this workflow.
  • OIDC trusted-publisher path unchanged: the permissions: id-token: write block at .github/workflows/pythonpublish.yml:13–15 (master) is preserved as-is. The PyPI trusted-publisher configuration accepts OIDC tokens issued for workflow_dispatch events on the same terms as for push events — no PyPI-side reconfiguration is needed.

Backward compatibility

  • The automatic push: tags: ["v*"] trigger at .github/workflows/pythonpublish.yml:4–7 (master) is unchanged; all future automatic releases behave identically.
  • fetch-depth: 0 is preserved so the full tag history remains available to uv build and to commitizen's own version introspection.
  • No Python source files are touched; uv run poe all reports no new lint or type errors.

Checklist

Was generative AI tooling used to co-author this PR?

  • Yes (please specify the tool below)

Generated-by: Claude following the guidelines

Code Changes

  • Add test cases to all the changes you introduce
  • Run uv run poe all locally to ensure this change passes linter check and tests
  • Manually test the changes (see "Steps to Test" below)
  • Update the documentation for the changes

Expected Behavior

Scenario Outcome
Normal tag push (e.g., v4.16.0) Workflow triggers automatically; inputs.ref is empty so github.ref_name is used — identical to pre-patch behaviour
Manual dispatch with ref = v4.11.1 Workflow checks out the v4.11.1 tag, runs uv build, and publishes to PyPI via the existing OIDC trusted-publisher flow
Manual dispatch from a fork deploy job is skipped; github.repository != 'commitizen-tools/commitizen'
Manual dispatch with no value in the ref field UI blocks submission because required: true is set

Steps to Test This Pull Request

git fetch fork fix/1790-publish-workflow-dispatch
git checkout fork/fix/1790-publish-workflow-dispatch

# 1. Targeted regression test — validate YAML syntax.
python -c "import yaml; yaml.safe_load(open('.github/workflows/pythonpublish.yml')); print('OK: YAML parses cleanly')"

# 2. Reproduce-the-bug-then-verify-the-fix sequence.
# Before this PR: republishing v4.11.1 requires recreating the tag.
# After merge: a maintainer can dispatch the workflow manually.
#
# Structural verification (can be run pre-merge):
python - <<'EOF'
import yaml
wf = yaml.safe_load(open(".github/workflows/pythonpublish.yml"))
triggers = wf["on"]
assert "push" in triggers, "push trigger missing"
assert "workflow_dispatch" in triggers, "workflow_dispatch trigger missing"
inp = triggers["workflow_dispatch"]["inputs"]["ref"]
assert inp["required"] is True, "ref input must be required"
assert inp["type"] == "string", "ref input must be type string"
steps = wf["jobs"]["deploy"]["steps"]
checkout = next(s for s in steps if "actions/checkout" in str(s.get("uses", "")))
assert "inputs.ref" in checkout["with"]["ref"], "checkout ref must use inputs.ref fallback"
guard = wf["jobs"]["deploy"]["if"]
assert "commitizen-tools/commitizen" in guard, "repo guard must still be present"
print("OK: workflow structure is correct")
EOF

# Post-merge functional test (requires maintainer access):
# 1. Go to https://github.com/commitizen-tools/commitizen/actions/workflows/pythonpublish.yml
# 2. Click "Run workflow" → enter "v4.11.1" in the ref field → "Run workflow"
# 3. Verify the run succeeds and https://pypi.org/project/commitizen/4.11.1/ becomes available

Additional Context

This fix was surfaced during the #1964 issue audit. The triage comment on #1790 confirmed that v4.11.1 remains absent from PyPI and that the original workflow run is too old to be re-run through the GitHub UI. The workflow_dispatch trigger is intentionally minimal — a single ref input, no pre-flight tag validation — to keep the diff small and the blast radius low. A potential follow-up hardening would add a git describe --exact-match --tags "$ref" pre-flight check to guard against accidentally dispatching with a branch name (PyPI would reject a duplicate version upload, but the build step would still run unnecessarily); that is left as a separate issue to keep this PR focused on unblocking the v4.11.1 republish.

The publish workflow only fires on tag push. When a publish run fails
and is older than 30 days, GitHub no longer allows re-running it -- the
tag has to be republished some other way. Adding a `workflow_dispatch`
trigger with a `ref` input lets maintainers re-publish a specific tag
from the Actions UI without recreating the tag (which would also affect
downstream automation).

Once this lands, a maintainer can republish `v4.11.1` (commitizen-tools#1790) by
triggering the workflow with `ref = v4.11.1`.

Closes commitizen-tools#1790

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.23%. Comparing base (4b93a50) to head (b07aa91).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1981   +/-   ##
=======================================
  Coverage   98.23%   98.23%           
=======================================
  Files          61       61           
  Lines        2779     2779           
=======================================
  Hits         2730     2730           
  Misses         49       49           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a manual dispatch “escape hatch” to the PyPI publishing workflow so maintainers can re-run publishing for an existing tag (e.g., v4.11.1) when the original tag-triggered run is too old to re-run via GitHub Actions UI/API.

Changes:

  • Add a workflow_dispatch trigger with a required ref input for selecting the tag to republish.
  • Update the actions/checkout step to use the dispatched ref when present, otherwise fall back to the tag from the push event.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/pythonpublish.yml Outdated
Comment thread .github/workflows/pythonpublish.yml
…atch

* switch ${{ inputs.ref ... }} to ${{ github.event.inputs.ref ... }} so the expression evaluates safely on push events too (avoids 'Unrecognized named-value: inputs' error)

* add a pre-checkout validation step on workflow_dispatch that fails fast if the supplied ref isn't an existing remote tag, so a branch name can't accidentally publish

* checkout via refs/tags/<ref> on dispatch as additional defence in depth

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4.11.1 is missing on PyPI

2 participants