Skip to content
Latchkey

Build Wheels and Release workflow (PyWavelets/pywt)

The Build Wheels and Release workflow from PyWavelets/pywt, explained and optimized by Latchkey.

D

CI health: D - needs work

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

Grade your own workflow free or run it on Latchkey →
Source: PyWavelets/pywt.github/workflows/wheel_tests_and_release.ymlLicense MITView source

What it does

This is the Build Wheels and Release workflow from the PyWavelets/pywt 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: Build Wheels and Release
on:
  push:
    tags:
      - "v*"
      - "buildwheels*"
    branches:
      # Runs on every merge to main to upload .dev0 wheels to anaconda.org
      - main
      - v1.**
  # Make it possible to upload wheels manually if needed (for anaconda.org only, not PyPI)
  workflow_dispatch:
    inputs:
      push_wheels:
        description: >
          Push wheels to Anaconda if "true". Default is "false". Warning: this will overwrite existing wheels.
        required: false
        default: "false"
  # Upload wheels to anaconda.org on a schedule
  schedule:
    # Run at 0300 hours on days 3 and 17 of the month
    - cron: "0 3 3,17 * *"
env:
  CIBW_BUILD_VERBOSITY: 2
  CIBW_TEST_REQUIRES: pytest
  CIBW_TEST_COMMAND: pytest --pyargs pywt -m "not slow"
  CIBW_ENVIRONMENT: PIP_PREFER_BINARY=1

jobs:
  build_linux_x86_64_wheels:
    name: Build ${{ matrix.cibw_python }} ${{ matrix.cibw_arch }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest]
        cibw_python: ["cp312", "cp313", "cp313t", "cp314", "cp314t"]
        cibw_arch: ["x86_64"]
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0
      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          python-version: "3.12"

      - name: Build the wheel
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_LINUX: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels_linux_${{ matrix.cibw_arch }}_${{ matrix.cibw_python }}
          path: ./dist/*.whl
          if-no-files-found: error

  build_linux_aarch64_wheels:
    name: Build ${{ matrix.cibw_python }} ${{ matrix.cibw_arch }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-22.04-arm]
        cibw_python: ["cp312", "cp313", "cp313t", "cp314", "cp314t"]
        cibw_arch: ["aarch64"]
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0
      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          python-version: "3.12"

      - name: Build the wheel
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_LINUX: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels_linux_${{ matrix.cibw_arch }}_${{ matrix.cibw_python }}
          path: ./dist/*.whl

  build_macos_wheels:
    name: Build ${{ matrix.cibw_python }} ${{ matrix.cibw_arch }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [macos-15-intel, macos-14]
        cibw_python: ["cp312", "cp313", "cp313t", "cp314", "cp314t"]
        cibw_arch: ["x86_64", "arm64"]
        exclude:
          - os: macos-14
            cibw_arch: "x86_64"
          - os: macos-15-intel
            cibw_arch: "arm64"
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0

      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          python-version: "3.12"

      - name: Build wheels for CPython (macOS) (x86_64)
        if: matrix.cibw_arch == 'x86_64'
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_MACOS: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading

      - name: Build wheels for CPython (macOS) (arm64)
        if: matrix.cibw_arch == 'arm64'
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_MACOS: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading

      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels_macos_${{ matrix.cibw_arch }}_${{ matrix.cibw_python }}
          path: ./dist/*.whl
          if-no-files-found: error

  build_windows_wheels:
    name: Build ${{ matrix.cibw_python }} ${{ matrix.cibw_arch }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [windows-latest, windows-11-arm]
        cibw_arch: ["AMD64", "x86", "ARM64"]
        cibw_python: ["cp312", "cp313", "cp313t", "cp314", "cp314t"]
        exclude:
          - os: windows-latest
            cibw_arch: ARM64
          - os: windows-11-arm
            cibw_arch: x86
          - os: windows-11-arm
            cibw_arch: AMD64
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0

      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          python-version: "3.12"

      - name: Setup MSVC (32-bit)
        if: matrix.cibw_arch == 'x86'
        uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1
        with:
          architecture: x86

      - name: Setup MSVC (64-bit)
        if: matrix.cibw_arch == 'AMD64'
        uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1
        with:
          architecture: x64

      - name: Setup MSVC (ARM64)
        if: matrix.cibw_arch == 'ARM64'
        uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1
        with:
          architecture: arm64

      - name: Build Windows wheels for CPython
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_WINDOWS: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading

      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels_windows_${{ matrix.cibw_arch }}_${{ matrix.cibw_python }}
          path: ./dist/*.whl
          if-no-files-found: error

  deploy_pypi:
    name: Release (PyPI)
    needs:
      [
        build_linux_x86_64_wheels,
        build_linux_aarch64_wheels,
        build_macos_wheels,
        build_windows_wheels,
      ]
    # Run only on tags pushed to the repository
    if: github.repository == 'PyWavelets/pywt' && startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0

      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          python-version: "3.12"

      - name: Install Twine
        run: |
          python -m pip install --upgrade pip
          pip install twine
          pip install cython numpy build

      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        id: download
        with:
          pattern: "wheels_*"
          path: ./dist
          merge-multiple: true

      - name: Publish the source distribution on PyPI
        run: |
          PYWT_VERSION=$(git describe --tags)
          python -m build --sdist
          ls -la ${{ github.workspace }}/dist
          # We prefer to release wheels before source because otherwise there is a
          # small window during which users who pip install pywt will require compilation.
          twine upload ${{ github.workspace }}/dist/*.whl
          twine upload ${{ github.workspace }}/dist/pywavelets-${PYWT_VERSION:1}.tar.gz
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.TWINE_TOKEN }}

      - name: Github release
        uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}

  deploy_anaconda:
    name: Release (Anaconda)
    needs:
      [
        build_linux_x86_64_wheels,
        build_linux_aarch64_wheels,
        build_macos_wheels,
        build_windows_wheels,
      ]
    # Run only on pushes to the main branch, on schedule, or when triggered manually
    if: >-
      github.repository == 'PyWavelets/pywt' &&
      (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
      (github.event_name == 'workflow_dispatch' && github.event.inputs.push_wheels == 'true') ||
      (github.event_name == 'schedule')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0

      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        id: download
        with:
          pattern: "wheels_*"
          path: dist/
          merge-multiple: true

      - name: Push to Anaconda PyPI index
        uses: scientific-python/upload-nightly-action@e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99 # v0.6.4
        with:
          artifacts_path: dist/
          anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

name: Build Wheels and Release
on:
  push:
    tags:
      - "v*"
      - "buildwheels*"
    branches:
      # Runs on every merge to main to upload .dev0 wheels to anaconda.org
      - main
      - v1.**
  # Make it possible to upload wheels manually if needed (for anaconda.org only, not PyPI)
  workflow_dispatch:
    inputs:
      push_wheels:
        description: >
          Push wheels to Anaconda if "true". Default is "false". Warning: this will overwrite existing wheels.
        required: false
        default: "false"
  # Upload wheels to anaconda.org on a schedule
  schedule:
    # Run at 0300 hours on days 3 and 17 of the month
    - cron: "0 3 3,17 * *"
env:
  CIBW_BUILD_VERBOSITY: 2
  CIBW_TEST_REQUIRES: pytest
  CIBW_TEST_COMMAND: pytest --pyargs pywt -m "not slow"
  CIBW_ENVIRONMENT: PIP_PREFER_BINARY=1
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  build_linux_x86_64_wheels:
    timeout-minutes: 30
    name: Build ${{ matrix.cibw_python }} ${{ matrix.cibw_arch }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest]
        cibw_python: ["cp312", "cp313", "cp313t", "cp314", "cp314t"]
        cibw_arch: ["x86_64"]
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0
      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          cache: 'pip'
          python-version: "3.12"
 
      - name: Build the wheel
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_LINUX: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels_linux_${{ matrix.cibw_arch }}_${{ matrix.cibw_python }}
          path: ./dist/*.whl
          if-no-files-found: error
 
  build_linux_aarch64_wheels:
    timeout-minutes: 30
    name: Build ${{ matrix.cibw_python }} ${{ matrix.cibw_arch }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-22.04-arm]
        cibw_python: ["cp312", "cp313", "cp313t", "cp314", "cp314t"]
        cibw_arch: ["aarch64"]
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0
      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          cache: 'pip'
          python-version: "3.12"
 
      - name: Build the wheel
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_LINUX: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels_linux_${{ matrix.cibw_arch }}_${{ matrix.cibw_python }}
          path: ./dist/*.whl
 
  build_macos_wheels:
    timeout-minutes: 30
    name: Build ${{ matrix.cibw_python }} ${{ matrix.cibw_arch }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [macos-15-intel, macos-14]
        cibw_python: ["cp312", "cp313", "cp313t", "cp314", "cp314t"]
        cibw_arch: ["x86_64", "arm64"]
        exclude:
          - os: macos-14
            cibw_arch: "x86_64"
          - os: macos-15-intel
            cibw_arch: "arm64"
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0
 
      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          cache: 'pip'
          python-version: "3.12"
 
      - name: Build wheels for CPython (macOS) (x86_64)
        if: matrix.cibw_arch == 'x86_64'
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_MACOS: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading
 
      - name: Build wheels for CPython (macOS) (arm64)
        if: matrix.cibw_arch == 'arm64'
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_MACOS: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading
 
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels_macos_${{ matrix.cibw_arch }}_${{ matrix.cibw_python }}
          path: ./dist/*.whl
          if-no-files-found: error
 
  build_windows_wheels:
    timeout-minutes: 30
    name: Build ${{ matrix.cibw_python }} ${{ matrix.cibw_arch }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [windows-latest, windows-11-arm]
        cibw_arch: ["AMD64", "x86", "ARM64"]
        cibw_python: ["cp312", "cp313", "cp313t", "cp314", "cp314t"]
        exclude:
          - os: windows-latest
            cibw_arch: ARM64
          - os: windows-11-arm
            cibw_arch: x86
          - os: windows-11-arm
            cibw_arch: AMD64
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0
 
      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          cache: 'pip'
          python-version: "3.12"
 
      - name: Setup MSVC (32-bit)
        if: matrix.cibw_arch == 'x86'
        uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1
        with:
          architecture: x86
 
      - name: Setup MSVC (64-bit)
        if: matrix.cibw_arch == 'AMD64'
        uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1
        with:
          architecture: x64
 
      - name: Setup MSVC (ARM64)
        if: matrix.cibw_arch == 'ARM64'
        uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1
        with:
          architecture: arm64
 
      - name: Build Windows wheels for CPython
        uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
        with:
          output-dir: dist
        env:
          CIBW_BUILD: ${{ matrix.cibw_python }}-*
          CIBW_ARCHS_WINDOWS: ${{ matrix.cibw_arch }}
          CIBW_ENABLE: cpython-freethreading
 
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wheels_windows_${{ matrix.cibw_arch }}_${{ matrix.cibw_python }}
          path: ./dist/*.whl
          if-no-files-found: error
 
  deploy_pypi:
    timeout-minutes: 30
    name: Release (PyPI)
    needs:
      [
        build_linux_x86_64_wheels,
        build_linux_aarch64_wheels,
        build_macos_wheels,
        build_windows_wheels,
      ]
    # Run only on tags pushed to the repository
    if: github.repository == 'PyWavelets/pywt' && startsWith(github.ref, 'refs/tags/v')
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0
 
      - uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v5.5.0
        name: Install Python
        with:
          cache: 'pip'
          python-version: "3.12"
 
      - name: Install Twine
        run: |
          python -m pip install --upgrade pip
          pip install twine
          pip install cython numpy build
 
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        id: download
        with:
          pattern: "wheels_*"
          path: ./dist
          merge-multiple: true
 
      - name: Publish the source distribution on PyPI
        run: |
          PYWT_VERSION=$(git describe --tags)
          python -m build --sdist
          ls -la ${{ github.workspace }}/dist
          # We prefer to release wheels before source because otherwise there is a
          # small window during which users who pip install pywt will require compilation.
          twine upload ${{ github.workspace }}/dist/*.whl
          twine upload ${{ github.workspace }}/dist/pywavelets-${PYWT_VERSION:1}.tar.gz
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.TWINE_TOKEN }}
 
      - name: Github release
        uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}
 
  deploy_anaconda:
    timeout-minutes: 30
    name: Release (Anaconda)
    needs:
      [
        build_linux_x86_64_wheels,
        build_linux_aarch64_wheels,
        build_macos_wheels,
        build_windows_wheels,
      ]
    # Run only on pushes to the main branch, on schedule, or when triggered manually
    if: >-
      github.repository == 'PyWavelets/pywt' &&
      (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
      (github.event_name == 'workflow_dispatch' && github.event.inputs.push_wheels == 'true') ||
      (github.event_name == 'schedule')
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.1.2
        with:
          fetch-depth: 0
 
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        id: download
        with:
          pattern: "wheels_*"
          path: dist/
          merge-multiple: true
 
      - name: Push to Anaconda PyPI index
        uses: scientific-python/upload-nightly-action@e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99 # v0.6.4
        with:
          artifacts_path: dist/
          anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
 

What changed

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 (62 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow