Skip to content
Latchkey

ci workflow (samuelcolvin/watchfiles)

The ci workflow from samuelcolvin/watchfiles, explained and optimized by Latchkey.

F

CI health: F - at risk

Point runs-on at Latchkey and get caching, 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: samuelcolvin/watchfiles.github/workflows/ci.ymlLicense MITView source

What it does

This is the ci workflow from the samuelcolvin/watchfiles 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: ci

on:
  push:
    branches:
      - main
    tags:
      - "**"
  pull_request: {}

env:
  COLUMNS: 120
  UV_PYTHON: 3.12
  UV_FROZEN: "1"

jobs:
  test:
    name: test ${{ matrix.python-version }}, rust ${{ matrix.rust-version }} on ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos, windows]
        rust-version: [stable, "1.83"]
        python-version:
          - "3.10"
          - "3.11"
          - "3.12"
          - "3.13"
          - "3.14"
          - "3.14t"
          - "3.15"
          - "pypy3.11"
        exclude:
          - rust-version: "1.83"
            os: macos
          - rust-version: "1.83"
            os: windows

    runs-on: ${{ matrix.os }}-latest

    env:
      UV_PYTHON: ${{ matrix.python-version }}
      RUST: ${{ matrix.rust-version }}
      OS: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: install rust
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: ${{ matrix.rust-version }}
          override: true

      - name: cache rust
        uses: Swatinem/rust-cache@v1

      - if: matrix.os == 'ubuntu'
        run: |
          mkdir -p ${{ github.workspace }}/protected
          touch ${{ github.workspace }}/protected/test
          sudo chown -R root:root ${{ github.workspace }}/protected
          sudo chmod 700 ${{ github.workspace }}/protected

      - run: uv run python -c 'import sys; print("free threading enable:", hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled())'

      - run: make test
        env:
          WATCHFILES_TEST_PERMISSION_DENIED_PATH: ${{ github.workspace }}/protected

      - run: uv run coverage xml

      - uses: codecov/codecov-action@v1.0.13
        with:
          file: ./coverage.xml
          env_vars: UV_PYTHON,RUST,OS

  lint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: install rust
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          override: true
          components: rustfmt, clippy

      - name: cache rust
        uses: Swatinem/rust-cache@v1

      - run: uv sync --group lint

      - uses: pre-commit/action@v3.0.0
        with:
          extra_args: --all-files --verbose
        env:
          SKIP: no-commit-to-branch

  docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - run: uv sync --group docs

      - run: make docs

      - name: store docs site
        uses: actions/upload-artifact@v4
        with:
          name: docs
          path: site

  build:
    name: >
      build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }})

    if: "!contains(github.event.pull_request.labels.*.name, 'Quick Build')"
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos, windows]
        target: [x86_64, aarch64]
        manylinux: [auto]
        include:
          - os: ubuntu
            platform: linux
            pypy: true
          - os: macos
            target: x86_64
            pypy: true
          - os: macos
            target: aarch64
            pypy: true
          - os: windows
            ls: dir
          # 3.15 is omitted from the Windows interpreter lists: pyo3's `generate-import-lib`
          # feature uses python3-dll-a, which (as bundled by pyo3 0.28.3) only supports
          # CPython up to 3.14, so generating the 3.15 import library fails. Restore 3.15
          # once pyo3 ships python3-dll-a >= 0.2.15. See:
          # https://github.com/samuelcolvin/watchfiles/pull/369#issuecomment-4474026284
          - os: windows
            ls: dir
            target: i686
            python-architecture: x86
            interpreter: 3.10 3.11 3.12 3.13 3.14
          - os: windows
            ls: dir
            target: x86_64
            interpreter: 3.10 3.11 3.12 3.13 3.14
          - os: windows
            ls: dir
            target: aarch64
            interpreter: 3.11 3.12 3.13 3.14
          - os: ubuntu
            platform: linux
            target: i686
          - os: ubuntu
            platform: linux
            target: armv7
          - os: ubuntu
            platform: linux
            target: ppc64le
          - os: ubuntu
            platform: linux
            target: s390x
          - os: ubuntu
            platform: linux
            target: riscv64gc-unknown-linux-gnu
          # musllinux
          - os: ubuntu
            platform: linux
            target: x86_64
            manylinux: musllinux_1_1
          - os: ubuntu
            platform: linux
            target: aarch64
            manylinux: musllinux_1_1

    runs-on: ${{ matrix.os }}-latest

    steps:
      - uses: actions/checkout@v5

      - name: set up python
        uses: actions/setup-python@v6
        with:
          python-version: "3.13"
          architecture: ${{ matrix.python-architecture || 'x64' }}

      - name: check GITHUB_REF matches package version
        uses: samuelcolvin/check-python-version@v4.1
        if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.os == 'ubuntu' }}
        with:
          version_file_path: Cargo.toml

      - name: build sdist
        if: ${{ matrix.os == 'ubuntu' && matrix.target == 'x86_64' && matrix.manylinux == 'auto' }}
        uses: PyO3/maturin-action@v1.49.3
        with:
          command: sdist
          args: --out dist

      - name: build wheels
        uses: PyO3/maturin-action@v1.49.3
        with:
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux || 'auto' }}
          args: --release --out dist --interpreter ${{ matrix.interpreter || '3.10 3.11 3.12 3.13 3.14 3.14t 3.15' }}

      - name: build pypy wheels
        if: ${{ matrix.pypy }}
        uses: PyO3/maturin-action@v1.49.3
        with:
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux || 'auto' }}
          args: --release --out dist --interpreter pypy3.11

      - run: ${{ matrix.ls || 'ls -lh' }} dist/

      - uses: actions/upload-artifact@v4
        with:
          name: pypi_files_${{ matrix.os }}_${{ matrix.target }}_${{ matrix.interpreter || 'all' }}_${{ matrix.manylinux }}
          path: dist

  list-pypi-files:
    needs: [build]
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist

      - name: list dist files
        run: |
          ls -lh dist/
          echo "`ls dist | wc -l` files"

      - name: extract and list sdist file
        run: |
          mkdir sdist-files
          tar -xvf dist/*.tar.gz -C sdist-files
          tree -a sdist-files

      - name: extract and list wheel file
        run: ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1

      - name: extract and list wheel file
        run: python -m zipfile --list `ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1`

  # Used for branch protection checks, see https://github.com/marketplace/actions/alls-green#why
  check:
    if: always()
    needs: [test, lint, docs]
    runs-on: ubuntu-latest
    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        with:
          jobs: ${{ toJSON(needs) }}

  release:
    needs: [build, check, docs]
    if: "success() && startsWith(github.ref, 'refs/tags/')"
    runs-on: ubuntu-latest
    environment: release

    permissions:
      id-token: write
      contents: write

    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist

      - name: get docs
        uses: actions/download-artifact@v4
        with:
          name: docs
          path: site

      - name: Publish to PyPI
        run: "uv publish --trusted-publishing always dist/*"

      - name: publish docs
        uses: JamesIves/github-pages-deploy-action@v4.7.2
        with:
          branch: gh-pages
          folder: site

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: ci
 
on:
  push:
    branches:
      - main
    tags:
      - "**"
  pull_request: {}
 
env:
  COLUMNS: 120
  UV_PYTHON: 3.12
  UV_FROZEN: "1"
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  test:
    timeout-minutes: 30
    name: test ${{ matrix.python-version }}, rust ${{ matrix.rust-version }} on ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos, windows]
        rust-version: [stable, "1.83"]
        python-version:
          - "3.10"
          - "3.11"
          - "3.12"
          - "3.13"
          - "3.14"
          - "3.14t"
          - "3.15"
          - "pypy3.11"
        exclude:
          - rust-version: "1.83"
            os: macos
          - rust-version: "1.83"
            os: windows
 
    runs-on: ${{ matrix.os }}-latest
 
    env:
      UV_PYTHON: ${{ matrix.python-version }}
      RUST: ${{ matrix.rust-version }}
      OS: ${{ matrix.os }}
 
    steps:
      - uses: actions/checkout@v4
 
      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true
 
      - name: install rust
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: ${{ matrix.rust-version }}
          override: true
 
      - name: cache rust
        uses: Swatinem/rust-cache@v1
 
      - if: matrix.os == 'ubuntu'
        run: |
          mkdir -p ${{ github.workspace }}/protected
          touch ${{ github.workspace }}/protected/test
          sudo chown -R root:root ${{ github.workspace }}/protected
          sudo chmod 700 ${{ github.workspace }}/protected
 
      - run: uv run python -c 'import sys; print("free threading enable:", hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled())'
 
      - run: make test
        env:
          WATCHFILES_TEST_PERMISSION_DENIED_PATH: ${{ github.workspace }}/protected
 
      - run: uv run coverage xml
 
      - uses: codecov/codecov-action@v1.0.13
        with:
          file: ./coverage.xml
          env_vars: UV_PYTHON,RUST,OS
 
  lint:
    timeout-minutes: 30
    runs-on: latchkey-small
 
    steps:
      - uses: actions/checkout@v4
 
      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true
 
      - name: install rust
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          override: true
          components: rustfmt, clippy
 
      - name: cache rust
        uses: Swatinem/rust-cache@v1
 
      - run: uv sync --group lint
 
      - uses: pre-commit/action@v3.0.0
        with:
          extra_args: --all-files --verbose
        env:
          SKIP: no-commit-to-branch
 
  docs:
    timeout-minutes: 30
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@v4
 
      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true
 
      - run: uv sync --group docs
 
      - run: make docs
 
      - name: store docs site
        uses: actions/upload-artifact@v4
        with:
          name: docs
          path: site
 
  build:
    timeout-minutes: 30
    name: >
      build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }})
 
    if: "!contains(github.event.pull_request.labels.*.name, 'Quick Build')"
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos, windows]
        target: [x86_64, aarch64]
        manylinux: [auto]
        include:
          - os: ubuntu
            platform: linux
            pypy: true
          - os: macos
            target: x86_64
            pypy: true
          - os: macos
            target: aarch64
            pypy: true
          - os: windows
            ls: dir
          # 3.15 is omitted from the Windows interpreter lists: pyo3's `generate-import-lib`
          # feature uses python3-dll-a, which (as bundled by pyo3 0.28.3) only supports
          # CPython up to 3.14, so generating the 3.15 import library fails. Restore 3.15
          # once pyo3 ships python3-dll-a >= 0.2.15. See:
          # https://github.com/samuelcolvin/watchfiles/pull/369#issuecomment-4474026284
          - os: windows
            ls: dir
            target: i686
            python-architecture: x86
            interpreter: 3.10 3.11 3.12 3.13 3.14
          - os: windows
            ls: dir
            target: x86_64
            interpreter: 3.10 3.11 3.12 3.13 3.14
          - os: windows
            ls: dir
            target: aarch64
            interpreter: 3.11 3.12 3.13 3.14
          - os: ubuntu
            platform: linux
            target: i686
          - os: ubuntu
            platform: linux
            target: armv7
          - os: ubuntu
            platform: linux
            target: ppc64le
          - os: ubuntu
            platform: linux
            target: s390x
          - os: ubuntu
            platform: linux
            target: riscv64gc-unknown-linux-gnu
          # musllinux
          - os: ubuntu
            platform: linux
            target: x86_64
            manylinux: musllinux_1_1
          - os: ubuntu
            platform: linux
            target: aarch64
            manylinux: musllinux_1_1
 
    runs-on: ${{ matrix.os }}-latest
 
    steps:
      - uses: actions/checkout@v5
 
      - name: set up python
        uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version: "3.13"
          architecture: ${{ matrix.python-architecture || 'x64' }}
 
      - name: check GITHUB_REF matches package version
        uses: samuelcolvin/check-python-version@v4.1
        if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.os == 'ubuntu' }}
        with:
          version_file_path: Cargo.toml
 
      - name: build sdist
        if: ${{ matrix.os == 'ubuntu' && matrix.target == 'x86_64' && matrix.manylinux == 'auto' }}
        uses: PyO3/maturin-action@v1.49.3
        with:
          command: sdist
          args: --out dist
 
      - name: build wheels
        uses: PyO3/maturin-action@v1.49.3
        with:
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux || 'auto' }}
          args: --release --out dist --interpreter ${{ matrix.interpreter || '3.10 3.11 3.12 3.13 3.14 3.14t 3.15' }}
 
      - name: build pypy wheels
        if: ${{ matrix.pypy }}
        uses: PyO3/maturin-action@v1.49.3
        with:
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux || 'auto' }}
          args: --release --out dist --interpreter pypy3.11
 
      - run: ${{ matrix.ls || 'ls -lh' }} dist/
 
      - uses: actions/upload-artifact@v4
        with:
          name: pypi_files_${{ matrix.os }}_${{ matrix.target }}_${{ matrix.interpreter || 'all' }}_${{ matrix.manylinux }}
          path: dist
 
  list-pypi-files:
    timeout-minutes: 30
    needs: [build]
    runs-on: latchkey-small
 
    steps:
      - uses: actions/checkout@v4
 
      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist
 
      - name: list dist files
        run: |
          ls -lh dist/
          echo "`ls dist | wc -l` files"
 
      - name: extract and list sdist file
        run: |
          mkdir sdist-files
          tar -xvf dist/*.tar.gz -C sdist-files
          tree -a sdist-files
 
      - name: extract and list wheel file
        run: ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1
 
      - name: extract and list wheel file
        run: python -m zipfile --list `ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1`
 
  # Used for branch protection checks, see https://github.com/marketplace/actions/alls-green#why
  check:
    timeout-minutes: 30
    if: always()
    needs: [test, lint, docs]
    runs-on: latchkey-small
    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        with:
          jobs: ${{ toJSON(needs) }}
 
  release:
    timeout-minutes: 30
    needs: [build, check, docs]
    if: "success() && startsWith(github.ref, 'refs/tags/')"
    runs-on: latchkey-small
    environment: release
 
    permissions:
      id-token: write
      contents: write
 
    steps:
      - uses: actions/checkout@v4
 
      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true
 
      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist
 
      - name: get docs
        uses: actions/download-artifact@v4
        with:
          name: docs
          path: site
 
      - name: Publish to PyPI
        run: "uv publish --trusted-publishing always dist/*"
 
      - name: publish docs
        uses: JamesIves/github-pages-deploy-action@v4.7.2
        with:
          branch: gh-pages
          folder: site
 

What changed

9 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

This workflow runs 7 jobs (59 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