From aa1848c3fdf6f6c4b31228b692cc3e2d7f1a706a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2026 19:14:03 +0200 Subject: [PATCH 1/4] Harden publish workflow recovery, notifications, and dispatch guard --- .github/workflows/publish.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 70b55df..081291f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ on: jobs: check: - if: github.repository == 'ultralytics/mkdocs' && github.actor == 'glenn-jocher' + if: github.repository == 'ultralytics/mkdocs' && github.actor == 'glenn-jocher' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: contents: write @@ -36,6 +36,7 @@ jobs: import os from actions.utils import check_pypi_version local_version, online_version, publish = check_pypi_version() + publish = publish or os.environ.get("GITHUB_EVENT_NAME") == "workflow_dispatch" # manual recovery re-run os.system(f'echo "increment={publish}" >> $GITHUB_OUTPUT') os.system(f'echo "current_tag=v{local_version}" >> $GITHUB_OUTPUT') os.system(f'echo "previous_tag=v{online_version}" >> $GITHUB_OUTPUT') @@ -49,11 +50,15 @@ jobs: PREVIOUS_TAG: ${{ steps.check_pypi.outputs.previous_tag }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | - git config --global user.name "UltralyticsAssistant" - git config --global user.email "web@ultralytics.com" - git tag -a "$CURRENT_TAG" -m "$(git log -1 --pretty=%B)" - git push origin "$CURRENT_TAG" - ultralytics-actions-summarize-release + if [ -z "$(git ls-remote --tags origin "refs/tags/$CURRENT_TAG")" ]; then + git config --global user.name "UltralyticsAssistant" + git config --global user.email "web@ultralytics.com" + git tag -a "$CURRENT_TAG" -m "$(git log -1 --pretty=%B)" + git push origin "$CURRENT_TAG" + fi + if ! gh release view "$CURRENT_TAG" >/dev/null 2>&1; then + ultralytics-actions-summarize-release + fi uv cache prune --ci build: @@ -91,6 +96,8 @@ jobs: name: dist path: dist/ - uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true # tolerate recovery re-runs after partial failures sbom: needs: [check, build, publish] @@ -114,12 +121,12 @@ jobs: format: spdx-json output-file: sbom.spdx.json path: sbom-env - - run: gh release upload ${{ needs.check.outputs.current_tag }} sbom.spdx.json + - run: gh release upload ${{ needs.check.outputs.current_tag }} sbom.spdx.json --clobber env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} notify: - needs: [check, publish] + needs: [check, publish, sbom] if: always() && needs.check.outputs.increment == 'True' runs-on: ubuntu-latest permissions: @@ -136,7 +143,7 @@ jobs: TITLE=$(printf '%s' "$TITLE" | sed -E "s@#([0-9]+)@@g") echo "title=$TITLE" >> "$GITHUB_OUTPUT" - name: Notify Success - if: needs.publish.result == 'success' && github.event_name == 'push' + if: needs.publish.result == 'success' && needs.sbom.result == 'success' && github.event_name == 'push' uses: slackapi/slack-github-action@v3.0.3 with: webhook-type: incoming-webhook @@ -144,7 +151,7 @@ jobs: payload: | text: " *${{ github.workflow }}* ✅ `${{ github.repository }}` ${{ steps.release.outputs.title || needs.check.outputs.current_tag }} · " - name: Notify Failure - if: needs.publish.result != 'success' + if: needs.publish.result != 'success' || needs.sbom.result != 'success' uses: slackapi/slack-github-action@v3.0.3 with: webhook-type: incoming-webhook From ade311a711fb587b00b44bffd8d9c00930a428a2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2026 19:20:25 +0200 Subject: [PATCH 2/4] Skip previous_tag output on recovery re-runs to avoid empty vX...vX changelog --- .github/workflows/publish.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 081291f..96f240c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -39,7 +39,8 @@ jobs: publish = publish or os.environ.get("GITHUB_EVENT_NAME") == "workflow_dispatch" # manual recovery re-run os.system(f'echo "increment={publish}" >> $GITHUB_OUTPUT') os.system(f'echo "current_tag=v{local_version}" >> $GITHUB_OUTPUT') - os.system(f'echo "previous_tag=v{online_version}" >> $GITHUB_OUTPUT') + if online_version != local_version: # empty on recovery re-runs so summarize falls back to the true previous tag + os.system(f'echo "previous_tag=v{online_version}" >> $GITHUB_OUTPUT') if publish: print('Ready to publish new version to PyPI ✅.') - name: Tag and Release From 8cb329a22f1146a73af3f47b891861de68cbeb73 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2026 19:24:44 +0200 Subject: [PATCH 3/4] Unshallow history before summarizing recovery releases --- .github/workflows/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 96f240c..1fa2dd0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -58,6 +58,7 @@ jobs: git push origin "$CURRENT_TAG" fi if ! gh release view "$CURRENT_TAG" >/dev/null 2>&1; then + [ -n "$PREVIOUS_TAG" ] || git fetch --unshallow --tags # summarize resolves the previous tag from git history ultralytics-actions-summarize-release fi uv cache prune --ci From 185e763bd9a5bd33e4391d9980043f30c38a3ead Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2026 19:46:18 +0200 Subject: [PATCH 4/4] Gate manual recovery path on the pypi dispatch input --- .github/workflows/publish.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1fa2dd0..3be63e6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,11 +32,13 @@ jobs: - run: uv pip install --system --no-cache ultralytics-actions - id: check_pypi shell: python + env: + PYPI_DISPATCH: ${{ github.event.inputs.pypi }} run: | import os from actions.utils import check_pypi_version local_version, online_version, publish = check_pypi_version() - publish = publish or os.environ.get("GITHUB_EVENT_NAME") == "workflow_dispatch" # manual recovery re-run + publish = publish or os.environ.get("PYPI_DISPATCH") == "true" # manual recovery re-run os.system(f'echo "increment={publish}" >> $GITHUB_OUTPUT') os.system(f'echo "current_tag=v{local_version}" >> $GITHUB_OUTPUT') if online_version != local_version: # empty on recovery re-runs so summarize falls back to the true previous tag