Skip to content

fix(release): repair the two post-publish jobs - #49

Closed
trsdn wants to merge 1 commit into
mainfrom
fix-release-postjobs
Closed

trsdn wants to merge 1 commit into
mainfrom
fix-release-postjobs

Conversation

@trsdn

@trsdn trsdn commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Follow-up to #48. The v2.0.0 release published to PyPI successfully via Trusted Publishing (trsdn-markitdown-mcp 2.0.0), and the GitHub release was created with both artifacts. Two jobs after the publish failed, though — this PR fixes both.

1. update-docs pushed to a protected branch

remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: - Changes must be made through a pull request.
 ! [remote rejected] main -> main (protected branch hook declined)

The job generated the changelog correctly and committed it, then tried git push origin main. GITHUB_TOKEN cannot bypass branch protection. It now pushes a docs/changelog-v<version> branch and opens a PR (idempotent — reuses an existing PR if the branch is already open). Added pull-requests: write to the job.

Deliberately not done: weakening branch protection or adding a bypass.

2. post-release-validation reported a release that exists as missing

🔍 Validating release completion for v2.0.0
❌ GitHub release missing

The release was published at 22:17:21Z; this check ran at 22:17:35Z and gh release view v2.0.0 still failed. Root cause: the job has no checkout step, so gh had no git remote to infer the repository from. Fixed with GH_REPO: ${{ github.repository }}.

Verified locally that the release does exist:

$ gh release view v2.0.0 --json name,isDraft,publishedAt,assets
name=Release 2.0.0 draft=false published=2026-08-24T22:17:21Z
  asset: trsdn_markitdown_mcp-2.0.0-py3-none-any.whl
  asset: trsdn_markitdown_mcp-2.0.0.tar.gz

3. Drive-by in the same step: pip index versions

That command is explicitly experimental and would have been the next failure in the same job. Replaced with a PyPI JSON API query, verified both directions:

$ curl -sf https://pypi.org/pypi/trsdn-markitdown-mcp/2.0.0/json > /dev/null   # exit 0
$ curl -sf https://pypi.org/pypi/trsdn-markitdown-mcp/9.9.9/json > /dev/null   # exit 22

Validation

actionlint .github/workflows/release.yml → 7 shellcheck findings, identical to the same file on main (all pre-existing, in untouched lines).

@trsdn

trsdn commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

Beide Root Causes sind hier richtig getroffen, und --force-with-lease sowie GH_REPO als Env statt -R am Aufruf finde ich sauberer als in #50. Ein Punkt sollte aber vor dem Merge raus, sonst bricht ein dokumentiertes Feature:

Die neuen Tag-Guards deaktivieren den workflow_dispatch-Re-Run-Pfad

Dieser PR ergänzt auf publish, create-github-release und post-release-validation:

if: startsWith(github.ref, 'refs/tags/')

Der Workflow hat aber einen required tag-Input:

workflow_dispatch:
  inputs:
    tag:
      description: 'Existing release tag to (re-)run the pipeline for, e.g. v1.2.3'
      required: true

und jeder Job checkt entsprechend ref: ${{ inputs.tag || github.ref }} aus (Zeilen 39, 91, 148, 191, 290, 488). Ein Dispatch ist hier also ausdrücklich kein Dry-Run, sondern der vorgesehene Weg, die Pipeline für einen bereits existierenden Tag erneut laufen zu lassen.

Bei workflow_dispatch ist github.ref aber refs/heads/main, nicht der Tag — die Bedingung ist also immer false. Ergebnis: ein Dispatch würde Quality Gates und Build durchlaufen und dann still bei publish abbrechen, ohne zu veröffentlichen und ohne Fehlermeldung.

Das ist genau der Pfad, der bei v2.0.0 gebraucht wurde: der Tag hat drei Anläufe gebraucht (21:56, 22:08, 22:12), weil das mypy --strict-Gate unter 3.12 an einem PEP-695-type-Statement in den numpy-Stubs scheiterte. Ohne Dispatch-Re-Run bleibt bei so einem Abbruch nur, den Tag zu löschen und neu zu pushen.

Ein Re-Run ist hier auch nicht gefährlich: validate-release hat bereits den Step „Check if tag already exists in releases", der mit exit 1 abbricht, wenn für den Tag schon ein Release existiert. Doppelte Uploads sind damit abgedeckt.

Vorschlag: die drei startsWith(github.ref, 'refs/tags/')-Guards wieder entfernen. Die beiden eigentlichen Fixes (GH_REPO und PR-statt-Push) sind davon unberührt.

Zum Abgleich: in trsdn/obsidian-mcp habe ich denselben Tag-Guard bewusst eingebaut (PR #11) — dort hat workflow_dispatch aber keinen Tag-Input, sodass ein Dispatch tatsächlich ungewollt die Version von main veröffentlicht hätte. Hier liegt der Fall genau andersherum.

Kleinigkeit

In #50 habe ich den PyPI-Check zusätzlich mit einem Retry versehen (5 Versuche, 15s Pause), weil der PyPI-Index nach dem Upload kurz nachhängen kann und der Job sonst an Eventual Consistency scheitert. Wäre hier auch sinnvoll.

@trsdn

trsdn commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

Ergänzt: kein PyPI-Upload aus workflow_dispatch

Hinweis aus einem Parallel-Repo aufgenommen (trsdn/paperless-mcp, trsdn/obsidian-mcp haben den Guard bereits).

Problem: release.yml akzeptiert workflow_dispatch, publish war aber nur auf is-prerelease gegated. Ein manueller Lauf wäre also bis zum Upload durchgelaufen und dort zwangsläufig gescheitert, weil PyPI eine bereits existierende Version ablehnt.

Fix — zusätzlich startsWith(github.ref, 'refs/tags/') auf drei Jobs:

Job Bedingung
publish startsWith(github.ref, 'refs/tags/') && needs.validate-release.outputs.is-prerelease == 'false'
create-github-release always() && startsWith(github.ref, 'refs/tags/') && …
post-release-validation always() && startsWith(github.ref, 'refs/tags/')

post-release-validation war ebenfalls nötig — mit if: always() hätte es bei jedem Dispatch am Ende "GitHub release missing" gemeldet. Ein manueller Dispatch ist damit ein sauberer Dry-Run aus Build + Quality Gates.

pre-release.yml: Tag-Guard bewusst nicht angewendet

Der Workflow hat ausschließlich workflow_dispatch als Trigger und erzeugt die rc/alpha/beta-Version selbst (inkl. Auto-Detection). Ein startsWith(github.ref, 'refs/tags/') würde ihn vollständig deaktivieren. Das Duplikat-Risiko auf TestPyPI besteht dort nur, wenn zweimal dieselbe Version generiert wird — eine andere Fragestellung als der Dispatch-Guard, die separat entschieden werden sollte.

Stattdessen dort die gleiche Least-Privilege-Lücke geschlossen, die in release.yml bereits behoben war — id-token: write stand workflow-weit:

# vorher (Workflow-Ebene)     # nachher (Workflow-Ebene)
permissions:                  permissions:
  contents: write               contents: read
  id-token: write             # test-pypi-upload:  { id-token: write }
  packages: write             # create-prerelease: { contents: write }

packages: write war ungenutzt und ist entfernt.

Validierung

actionlint auf beiden Dateien mit identischer Befundzahl wie main (release.yml 7/7, pre-release.yml 5/5 — alle vorbestehend in unveränderten Zeilen). Alle drei if-Ausdrücke per YAML-Parse gegengeprüft.

`release.yml` accepts `workflow_dispatch`, but `publish` was only gated on
`is-prerelease`. A manual run therefore proceeded all the way to the upload and
would always fail, because PyPI rejects re-uploading an existing version.

- `publish`, `create-github-release` and `post-release-validation` are now
  additionally gated on `startsWith(github.ref, 'refs/tags/')`. A manual
  dispatch becomes a dry run of build + quality gates with no upload attempt and
  no "GitHub release missing" failure at the end.
- `pre-release.yml` is dispatch-only by design (it generates the rc/alpha/beta
  version itself), so a tag guard would disable it outright and is deliberately
  not applied. Its workflow-level `id-token: write` / `contents: write` /
  `packages: write` is rescoped instead: the default is now `contents: read`,
  `id-token: write` lives only in `test-pypi-upload` and `contents: write` only
  in `create-prerelease`. Unused `packages: write` removed.

Matches the guard already present in trsdn/paperless-mcp and trsdn/obsidian-mcp.
The post-publish job fixes previously in this branch landed via #50; this branch
was rebuilt on top of main to keep only the remaining change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@trsdn
trsdn force-pushed the fix-release-postjobs branch from 39a2480 to 40666d1 Compare August 24, 2026 22:30
@trsdn

trsdn commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

Überholt durch #50, das inzwischen gemergt ist und beide Root Causes behebt (fehlender Repo-Kontext für gh im Job ohne Checkout, sowie Direkt-Push auf den geschützten main).

Die zwei Details, die hier sauberer gelöst waren, sind übernommen und liegen als #51 zum Merge bereit: --force-with-lease statt --force beim Branch-Push, und GH_REPO als Step-Env statt -R am einzelnen Aufruf.

Nicht übernommen wurden bewusst die zusätzlichen if: startsWith(github.ref, 'refs/tags/')-Guards auf publish, create-github-release und post-release-validation — Begründung im Kommentar oben: dieser Workflow hat einen required tag-Input bei workflow_dispatch, sodass der Dispatch der vorgesehene Re-Run-Pfad für einen existierenden Tag ist. Die Guards hätten ihn still deaktiviert.

Danke — hier geht nichts verloren, nur konsolidiert.

@trsdn trsdn closed this Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 PR Quality Summary

CI Status

🔄 Workflows in progress...

Metrics

Metric Value Trend
📊 Coverage N/A -
🧪 Tests Test results unavailable -
⏱️ Performance No performance data -

Quality Checks

  • Format & Lint: Ruff formatting and linting
  • Type Safety: MyPy strict type checking
  • Security: Bandit, Safety, GitLeaks scanning
  • MCP Protocol: Tool schema validation
  • Documentation: Docstring coverage (80%+)

MCP Tools

  • convert_file - Convert individual files to Markdown
  • convert_directory - Batch convert directories
  • list_supported_formats - Query supported file types

🤖 Auto-generated by CI • Last updated: 2026-08-24 22:30 UTC

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