Skip to content
Latchkey

Publish to PyPI workflow (avibe-bot/avibe)

The Publish to PyPI workflow from avibe-bot/avibe, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: avibe-bot/avibe.github/workflows/publish.ymlLicense MITView source

What it does

This is the Publish to PyPI workflow from the avibe-bot/avibe repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Publish to PyPI

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to publish (e.g., v2.0.0 or v2.0.1rc1)"
        required: true
        type: string

permissions:
  contents: read

jobs:
  resolve-tag:
    runs-on: ubuntu-latest
    outputs:
      tag: ${{ steps.tag.outputs.tag }}
      publish_legacy: ${{ steps.tag.outputs.publish_legacy }}
    steps:
      - name: Resolve tag
        id: tag
        shell: bash
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            TAG="${{ github.event.inputs.tag }}"
          else
            TAG="${GITHUB_REF_NAME}"
          fi

          if [ -z "$TAG" ]; then
            echo "Tag is empty" >&2
            exit 1
          fi

          PUBLISH_LEGACY="false"
          if [[ "$TAG" =~ ^v3\.0\.[0-9]+$ ]]; then
            PUBLISH_LEGACY="true"
          fi

          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "publish_legacy=$PUBLISH_LEGACY" >> "$GITHUB_OUTPUT"

  resolve-show-runtime-ref:
    runs-on: ubuntu-latest
    outputs:
      ref: ${{ steps.ref.outputs.ref }}
    steps:
      - name: Resolve Show Runtime ref
        id: ref
        env:
          GH_TOKEN: ${{ github.token }}
        shell: bash
        run: |
          REF=$(gh api repos/avibe-bot/vibe-show-runtime/commits/main --jq '.sha')
          if [ -z "$REF" ]; then
            echo "Failed to resolve avibe-bot/vibe-show-runtime main" >&2
            exit 1
          fi
          echo "ref=$REF" >> "$GITHUB_OUTPUT"

  show-runtime-bundles:
    needs: resolve-show-runtime-ref
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            artifact: linux-x64
          - os: ubuntu-24.04-arm
            artifact: linux-arm64
          - os: macos-15-intel
            artifact: darwin-x64
          - os: macos-14
            artifact: darwin-arm64
          - os: windows-latest
            artifact: win32-x64
          - os: windows-11-arm
            artifact: win32-arm64
    steps:
      - name: Checkout Show Runtime
        uses: actions/checkout@v5
        with:
          repository: avibe-bot/vibe-show-runtime
          ref: ${{ needs.resolve-show-runtime-ref.outputs.ref }}

      - name: Setup Node.js
        uses: actions/setup-node@v5
        with:
          node-version: "22.14.0"
          cache: "npm"

      - name: Build Show Runtime bundle
        run: |
          npm ci
          npm run build
          npm run bundle:vibe-remote
          cp dist/vibe-show-runtime-node-*.tgz .
          git rev-parse HEAD > show-runtime-ref-${{ matrix.artifact }}.txt

      - name: Upload Show Runtime bundle
        uses: actions/upload-artifact@v6
        with:
          name: show-runtime-${{ matrix.artifact }}
          path: |
            vibe-show-runtime-node-*.tgz
            show-runtime-ref-${{ matrix.artifact }}.txt

  build:
    needs:
      - resolve-tag
      - show-runtime-bundles
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@v5
        with:
          ref: ${{ needs.resolve-tag.outputs.tag }}

      - name: Setup Node.js
        uses: actions/setup-node@v5
        with:
          node-version: "22.14.0"
          cache: "npm"
          cache-dependency-path: ui/package-lock.json

      - name: Build UI
        working-directory: ui
        run: |
          npm ci
          npm run build

      - name: Download Show Runtime bundles
        uses: actions/download-artifact@v6
        with:
          pattern: show-runtime-*
          path: runtime-artifacts
          merge-multiple: true

      - name: Generate Show Runtime manifest
        run: |
          python scripts/generate_show_runtime_manifest.py \
            --archive-dir runtime-artifacts \
            --tag "${{ needs.resolve-tag.outputs.tag }}" \
            --repo "${{ github.repository }}" \
            --runtime-ref-file 'runtime-artifacts/show-runtime-ref-*.txt' \
            --output vibe/show_runtime_manifest.json
          cp vibe/show_runtime_manifest.json runtime-artifacts/show-runtime-manifest.json

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"

      - name: Verify Show Runtime release assets
        run: |
          python - <<'PY'
          import hashlib
          import json
          from pathlib import Path

          manifest_path = Path("vibe/show_runtime_manifest.json")
          manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
          archives = manifest.get("archives") or {}
          expected = {
              "darwin-arm64",
              "darwin-x64",
              "linux-arm64",
              "linux-x64",
              "win32-arm64",
              "win32-x64",
          }
          missing = expected - set(archives)
          if missing:
              raise SystemExit("Manifest is missing platforms: " + ", ".join(sorted(missing)))

          def sha256(path: Path) -> str:
              digest = hashlib.sha256()
              with path.open("rb") as file:
                  for chunk in iter(lambda: file.read(1024 * 1024), b""):
                      digest.update(chunk)
              return digest.hexdigest()

          for platform, archive in sorted(archives.items()):
              path = Path("runtime-artifacts") / archive["name"]
              if not path.exists():
                  raise SystemExit(f"Missing release archive for {platform}: {archive['name']}")
              if archive.get("size") is not None and path.stat().st_size != archive["size"]:
                  raise SystemExit(f"Size mismatch for {archive['name']}")
              if sha256(path) != archive["sha256"]:
                  raise SystemExit(f"Checksum mismatch for {archive['name']}")
          PY

      - name: Install build and test dependencies
        run: pip install -e . build pytest

      - name: Build package
        run: python -m build

      - name: Build 3.0 compatibility legacy PyPI shim
        shell: bash
        run: |
          TAG="${{ needs.resolve-tag.outputs.tag }}"
          if [ "${{ needs.resolve-tag.outputs.publish_legacy }}" = "true" ]; then
            python scripts/prepare_legacy_pypi_shim.py --tag "$TAG"
            python -m build --outdir dist packaging/vibe-remote-shim
          else
            echo "Skipping legacy vibe-remote shim for $TAG"
          fi

      - name: Verify package Show Runtime manifest
        run: |
          python - <<'PY'
          import zipfile
          from pathlib import Path

          wheels = [
              wheel
              for wheel in sorted(Path("dist").glob("*.whl"))
              if wheel.name.startswith("avibe_os-")
          ]
          if len(wheels) != 1:
              raise SystemExit(f"Expected exactly one avibe-os wheel, found {len(wheels)}")

          with zipfile.ZipFile(wheels[0]) as wheel:
              names = set(wheel.namelist())
              archives = {
                  name for name in names
                  if name.startswith("vibe/show_runtime/") and name.endswith(".tgz")
              }

          if archives:
              raise SystemExit("Wheel unexpectedly contains Show Runtime archives:\n" + "\n".join(sorted(archives)))
          if "vibe/show_runtime_manifest.json" not in names:
              raise SystemExit("Wheel is missing vibe/show_runtime_manifest.json")
          PY

      - name: Run release install and upgrade regressions
        run: |
          docker info
          export VIBE_INSTALL_TEST_WHEEL="$(ls dist/avibe_os-*.whl)"
          pytest \
            tests/test_upgrade_flow.py \
            tests/test_install_script.py \
            tests/e2e/test_install_command.py \
            tests/e2e/test_upgrade_command.py \
            -v

      - name: Upload GitHub release assets
        env:
          GH_TOKEN: ${{ github.token }}
        shell: bash
        run: |
          TAG="${{ needs.resolve-tag.outputs.tag }}"
          PRERELEASE="false"
          if [[ "$TAG" == *-* ]]; then
            PRERELEASE="true"
          elif [[ "$TAG" =~ ^v[0-9]+(\.[0-9]+)*(([.-])?(a|b|rc|dev)[0-9]+)$ ]]; then
            PRERELEASE="true"
          fi

          if ! gh release view "$TAG" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
            ARGS=(release create "$TAG" --repo "${GITHUB_REPOSITORY}" --title "$TAG" --notes "Release assets for $TAG.")
            if [ "$PRERELEASE" = "true" ]; then
              ARGS+=(--prerelease)
            fi
            gh "${ARGS[@]}" || gh release view "$TAG" --repo "${GITHUB_REPOSITORY}" >/dev/null
          fi

          # Show Runtime archives + manifest are IMMUTABLE per tag and must NOT be
          # re-clobbered. The runtime tarball is not byte-reproducible, so a re-run of
          # this workflow rebuilds different bytes, while the manifest baked into the wheel
          # is published to PyPI immutably (skip-existing). Clobbering the release archive
          # with a fresh build would desync the published wheel's manifest from the hosted
          # archive, and every Show Runtime install would fail its integrity check
          # (runtime_archive_size_mismatch).
          #
          # Verify immutability BEFORE mutating ANY release asset (including dist/*), so a
          # mismatch fails the job without having already replaced the wheel/sdist. Upload
          # each runtime asset only if absent; if it already exists, require byte-identity.
          existing="$(gh release view "$TAG" --repo "${GITHUB_REPOSITORY}" --json assets --jq '.assets[].name')"
          tmp="$(mktemp -d)"
          runtime_to_upload=()
          for path in runtime-artifacts/vibe-show-runtime-node-*.tgz runtime-artifacts/show-runtime-manifest.json; do
            name="$(basename "$path")"
            if grep -qxF "$name" <<<"$existing"; then
              rm -f "$tmp/$name"
              gh release download "$TAG" --repo "${GITHUB_REPOSITORY}" --pattern "$name" --dir "$tmp" --clobber
              if cmp -s "$tmp/$name" "$path"; then
                echo "Release asset '$name' already present and byte-identical; skipping (immutable per tag)."
              else
                echo "::error::Release asset '$name' already exists for $TAG but differs from this run's build." >&2
                echo "::error::The Show Runtime tarball is not byte-reproducible and this run rebuilt it; overwriting would desync the release from the PyPI wheel's manifest. Cut a NEW version instead of re-running the release." >&2
                exit 1
              fi
            else
              runtime_to_upload+=("$path")
            fi
          done

          # Immutability gate passed - only now is it safe to mutate the release.
          if [ "${#runtime_to_upload[@]}" -gt 0 ]; then
            gh release upload "$TAG" --repo "${GITHUB_REPOSITORY}" "${runtime_to_upload[@]}"
          fi
          # Python distributions are safe to refresh (PyPI itself is skip-existing).
          gh release upload "$TAG" --repo "${GITHUB_REPOSITORY}" dist/* --clobber

      - name: Upload artifacts
        uses: actions/upload-artifact@v6
        with:
          name: dist
          path: dist/

  publish-avibe-os:
    needs: build
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write  # Required for trusted publishing
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v6
        with:
          name: dist
          path: dist/

      - name: Keep avibe-os distributions only
        shell: bash
        run: |
          find dist -type f ! \( -name 'avibe_os-*' -o -name 'avibe-os-*' \) -delete
          test -n "$(find dist -maxdepth 1 -type f -name 'avibe_os-*' -print -quit)"

      - name: Publish avibe-os to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          skip-existing: true
        # Uses trusted publishing (OIDC), no API token needed
        # Configure at: https://pypi.org/manage/project/avibe-os/settings/publishing/

  publish-vibe-remote:
    needs:
      - resolve-tag
      - build
    if: needs.resolve-tag.outputs.publish_legacy == 'true'
    runs-on: ubuntu-latest
    # PyPI trusted publishers issue project-scoped tokens for the exact OIDC
    # claim, including the GitHub environment. Keep the legacy shim on a
    # separate environment so it can be configured for the vibe-remote project
    # without colliding with the avibe-os publisher.
    environment: pypi-vibe-remote
    permissions:
      id-token: write  # Required for trusted publishing
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v6
        with:
          name: dist
          path: dist/

      - name: Keep vibe-remote distributions only
        shell: bash
        run: |
          find dist -type f ! \( -name 'vibe_remote-*' -o -name 'vibe-remote-*' \) -delete
          test -n "$(find dist -maxdepth 1 -type f -name 'vibe_remote-*' -print -quit)"

      - name: Publish vibe-remote to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          skip-existing: true
        # Uses trusted publishing (OIDC), no API token needed
        # Configure at: https://pypi.org/manage/project/vibe-remote/settings/publishing/

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: Publish to PyPI
 
on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      tag:
        description: "Tag to publish (e.g., v2.0.0 or v2.0.1rc1)"
        required: true
        type: string
 
permissions:
  contents: read
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  resolve-tag:
    timeout-minutes: 30
    runs-on: latchkey-small
    outputs:
      tag: ${{ steps.tag.outputs.tag }}
      publish_legacy: ${{ steps.tag.outputs.publish_legacy }}
    steps:
      - name: Resolve tag
        id: tag
        shell: bash
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            TAG="${{ github.event.inputs.tag }}"
          else
            TAG="${GITHUB_REF_NAME}"
          fi
 
          if [ -z "$TAG" ]; then
            echo "Tag is empty" >&2
            exit 1
          fi
 
          PUBLISH_LEGACY="false"
          if [[ "$TAG" =~ ^v3\.0\.[0-9]+$ ]]; then
            PUBLISH_LEGACY="true"
          fi
 
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "publish_legacy=$PUBLISH_LEGACY" >> "$GITHUB_OUTPUT"
 
  resolve-show-runtime-ref:
    timeout-minutes: 30
    runs-on: latchkey-small
    outputs:
      ref: ${{ steps.ref.outputs.ref }}
    steps:
      - name: Resolve Show Runtime ref
        id: ref
        env:
          GH_TOKEN: ${{ github.token }}
        shell: bash
        run: |
          REF=$(gh api repos/avibe-bot/vibe-show-runtime/commits/main --jq '.sha')
          if [ -z "$REF" ]; then
            echo "Failed to resolve avibe-bot/vibe-show-runtime main" >&2
            exit 1
          fi
          echo "ref=$REF" >> "$GITHUB_OUTPUT"
 
  show-runtime-bundles:
    timeout-minutes: 30
    needs: resolve-show-runtime-ref
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            artifact: linux-x64
          - os: ubuntu-24.04-arm
            artifact: linux-arm64
          - os: macos-15-intel
            artifact: darwin-x64
          - os: macos-14
            artifact: darwin-arm64
          - os: windows-latest
            artifact: win32-x64
          - os: windows-11-arm
            artifact: win32-arm64
    steps:
      - name: Checkout Show Runtime
        uses: actions/checkout@v5
        with:
          repository: avibe-bot/vibe-show-runtime
          ref: ${{ needs.resolve-show-runtime-ref.outputs.ref }}
 
      - name: Setup Node.js
        uses: actions/setup-node@v5
        with:
          node-version: "22.14.0"
          cache: "npm"
 
      - name: Build Show Runtime bundle
        run: |
          npm ci
          npm run build
          npm run bundle:vibe-remote
          cp dist/vibe-show-runtime-node-*.tgz .
          git rev-parse HEAD > show-runtime-ref-${{ matrix.artifact }}.txt
 
      - name: Upload Show Runtime bundle
        uses: actions/upload-artifact@v6
        with:
          name: show-runtime-${{ matrix.artifact }}
          path: |
            vibe-show-runtime-node-*.tgz
            show-runtime-ref-${{ matrix.artifact }}.txt
 
  build:
    timeout-minutes: 30
    needs:
      - resolve-tag
      - show-runtime-bundles
    runs-on: latchkey-small
    permissions:
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@v5
        with:
          ref: ${{ needs.resolve-tag.outputs.tag }}
 
      - name: Setup Node.js
        uses: actions/setup-node@v5
        with:
          node-version: "22.14.0"
          cache: "npm"
          cache-dependency-path: ui/package-lock.json
 
      - name: Build UI
        working-directory: ui
        run: |
          npm ci
          npm run build
 
      - name: Download Show Runtime bundles
        uses: actions/download-artifact@v6
        with:
          pattern: show-runtime-*
          path: runtime-artifacts
          merge-multiple: true
 
      - name: Generate Show Runtime manifest
        run: |
          python scripts/generate_show_runtime_manifest.py \
            --archive-dir runtime-artifacts \
            --tag "${{ needs.resolve-tag.outputs.tag }}" \
            --repo "${{ github.repository }}" \
            --runtime-ref-file 'runtime-artifacts/show-runtime-ref-*.txt' \
            --output vibe/show_runtime_manifest.json
          cp vibe/show_runtime_manifest.json runtime-artifacts/show-runtime-manifest.json
 
      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"
 
      - name: Verify Show Runtime release assets
        run: |
          python - <<'PY'
          import hashlib
          import json
          from pathlib import Path
 
          manifest_path = Path("vibe/show_runtime_manifest.json")
          manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
          archives = manifest.get("archives") or {}
          expected = {
              "darwin-arm64",
              "darwin-x64",
              "linux-arm64",
              "linux-x64",
              "win32-arm64",
              "win32-x64",
          }
          missing = expected - set(archives)
          if missing:
              raise SystemExit("Manifest is missing platforms: " + ", ".join(sorted(missing)))
 
          def sha256(path: Path) -> str:
              digest = hashlib.sha256()
              with path.open("rb") as file:
                  for chunk in iter(lambda: file.read(1024 * 1024), b""):
                      digest.update(chunk)
              return digest.hexdigest()
 
          for platform, archive in sorted(archives.items()):
              path = Path("runtime-artifacts") / archive["name"]
              if not path.exists():
                  raise SystemExit(f"Missing release archive for {platform}: {archive['name']}")
              if archive.get("size") is not None and path.stat().st_size != archive["size"]:
                  raise SystemExit(f"Size mismatch for {archive['name']}")
              if sha256(path) != archive["sha256"]:
                  raise SystemExit(f"Checksum mismatch for {archive['name']}")
          PY
 
      - name: Install build and test dependencies
        run: pip install -e . build pytest
 
      - name: Build package
        run: python -m build
 
      - name: Build 3.0 compatibility legacy PyPI shim
        shell: bash
        run: |
          TAG="${{ needs.resolve-tag.outputs.tag }}"
          if [ "${{ needs.resolve-tag.outputs.publish_legacy }}" = "true" ]; then
            python scripts/prepare_legacy_pypi_shim.py --tag "$TAG"
            python -m build --outdir dist packaging/vibe-remote-shim
          else
            echo "Skipping legacy vibe-remote shim for $TAG"
          fi
 
      - name: Verify package Show Runtime manifest
        run: |
          python - <<'PY'
          import zipfile
          from pathlib import Path
 
          wheels = [
              wheel
              for wheel in sorted(Path("dist").glob("*.whl"))
              if wheel.name.startswith("avibe_os-")
          ]
          if len(wheels) != 1:
              raise SystemExit(f"Expected exactly one avibe-os wheel, found {len(wheels)}")
 
          with zipfile.ZipFile(wheels[0]) as wheel:
              names = set(wheel.namelist())
              archives = {
                  name for name in names
                  if name.startswith("vibe/show_runtime/") and name.endswith(".tgz")
              }
 
          if archives:
              raise SystemExit("Wheel unexpectedly contains Show Runtime archives:\n" + "\n".join(sorted(archives)))
          if "vibe/show_runtime_manifest.json" not in names:
              raise SystemExit("Wheel is missing vibe/show_runtime_manifest.json")
          PY
 
      - name: Run release install and upgrade regressions
        run: |
          docker info
          export VIBE_INSTALL_TEST_WHEEL="$(ls dist/avibe_os-*.whl)"
          pytest \
            tests/test_upgrade_flow.py \
            tests/test_install_script.py \
            tests/e2e/test_install_command.py \
            tests/e2e/test_upgrade_command.py \
            -v
 
      - name: Upload GitHub release assets
        env:
          GH_TOKEN: ${{ github.token }}
        shell: bash
        run: |
          TAG="${{ needs.resolve-tag.outputs.tag }}"
          PRERELEASE="false"
          if [[ "$TAG" == *-* ]]; then
            PRERELEASE="true"
          elif [[ "$TAG" =~ ^v[0-9]+(\.[0-9]+)*(([.-])?(a|b|rc|dev)[0-9]+)$ ]]; then
            PRERELEASE="true"
          fi
 
          if ! gh release view "$TAG" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
            ARGS=(release create "$TAG" --repo "${GITHUB_REPOSITORY}" --title "$TAG" --notes "Release assets for $TAG.")
            if [ "$PRERELEASE" = "true" ]; then
              ARGS+=(--prerelease)
            fi
            gh "${ARGS[@]}" || gh release view "$TAG" --repo "${GITHUB_REPOSITORY}" >/dev/null
          fi
 
          # Show Runtime archives + manifest are IMMUTABLE per tag and must NOT be
          # re-clobbered. The runtime tarball is not byte-reproducible, so a re-run of
          # this workflow rebuilds different bytes, while the manifest baked into the wheel
          # is published to PyPI immutably (skip-existing). Clobbering the release archive
          # with a fresh build would desync the published wheel's manifest from the hosted
          # archive, and every Show Runtime install would fail its integrity check
          # (runtime_archive_size_mismatch).
          #
          # Verify immutability BEFORE mutating ANY release asset (including dist/*), so a
          # mismatch fails the job without having already replaced the wheel/sdist. Upload
          # each runtime asset only if absent; if it already exists, require byte-identity.
          existing="$(gh release view "$TAG" --repo "${GITHUB_REPOSITORY}" --json assets --jq '.assets[].name')"
          tmp="$(mktemp -d)"
          runtime_to_upload=()
          for path in runtime-artifacts/vibe-show-runtime-node-*.tgz runtime-artifacts/show-runtime-manifest.json; do
            name="$(basename "$path")"
            if grep -qxF "$name" <<<"$existing"; then
              rm -f "$tmp/$name"
              gh release download "$TAG" --repo "${GITHUB_REPOSITORY}" --pattern "$name" --dir "$tmp" --clobber
              if cmp -s "$tmp/$name" "$path"; then
                echo "Release asset '$name' already present and byte-identical; skipping (immutable per tag)."
              else
                echo "::error::Release asset '$name' already exists for $TAG but differs from this run's build." >&2
                echo "::error::The Show Runtime tarball is not byte-reproducible and this run rebuilt it; overwriting would desync the release from the PyPI wheel's manifest. Cut a NEW version instead of re-running the release." >&2
                exit 1
              fi
            else
              runtime_to_upload+=("$path")
            fi
          done
 
          # Immutability gate passed - only now is it safe to mutate the release.
          if [ "${#runtime_to_upload[@]}" -gt 0 ]; then
            gh release upload "$TAG" --repo "${GITHUB_REPOSITORY}" "${runtime_to_upload[@]}"
          fi
          # Python distributions are safe to refresh (PyPI itself is skip-existing).
          gh release upload "$TAG" --repo "${GITHUB_REPOSITORY}" dist/* --clobber
 
      - name: Upload artifacts
        uses: actions/upload-artifact@v6
        with:
          name: dist
          path: dist/
 
  publish-avibe-os:
    timeout-minutes: 30
    needs: build
    runs-on: latchkey-small
    environment: pypi
    permissions:
      id-token: write  # Required for trusted publishing
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v6
        with:
          name: dist
          path: dist/
 
      - name: Keep avibe-os distributions only
        shell: bash
        run: |
          find dist -type f ! \( -name 'avibe_os-*' -o -name 'avibe-os-*' \) -delete
          test -n "$(find dist -maxdepth 1 -type f -name 'avibe_os-*' -print -quit)"
 
      - name: Publish avibe-os to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          skip-existing: true
        # Uses trusted publishing (OIDC), no API token needed
        # Configure at: https://pypi.org/manage/project/avibe-os/settings/publishing/
 
  publish-vibe-remote:
    timeout-minutes: 30
    needs:
      - resolve-tag
      - build
    if: needs.resolve-tag.outputs.publish_legacy == 'true'
    runs-on: latchkey-small
    # PyPI trusted publishers issue project-scoped tokens for the exact OIDC
    # claim, including the GitHub environment. Keep the legacy shim on a
    # separate environment so it can be configured for the vibe-remote project
    # without colliding with the avibe-os publisher.
    environment: pypi-vibe-remote
    permissions:
      id-token: write  # Required for trusted publishing
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v6
        with:
          name: dist
          path: dist/
 
      - name: Keep vibe-remote distributions only
        shell: bash
        run: |
          find dist -type f ! \( -name 'vibe_remote-*' -o -name 'vibe-remote-*' \) -delete
          test -n "$(find dist -maxdepth 1 -type f -name 'vibe_remote-*' -print -quit)"
 
      - name: Publish vibe-remote to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          skip-existing: true
        # Uses trusted publishing (OIDC), no API token needed
        # Configure at: https://pypi.org/manage/project/vibe-remote/settings/publishing/
 

What changed

1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 6 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow