Skip to content
Latchkey

CI workflow (hikari-py/hikari)

The CI workflow from hikari-py/hikari, explained and optimized by Latchkey.

A

CI health: A - excellent

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

What it does

This is the CI workflow from the hikari-py/hikari 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:
      - master
  pull_request:
    branches:
      - master

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    strategy:
      # Allows for matrix sub-jobs to fail without cancelling the rest
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.10", 3.11, 3.12, 3.13, 3.14]

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

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          python-version: ${{ matrix.python-version }}
          activate-environment: true
          # TODO(https://github.com/astral-sh/setup-uv/issues/226): Remove this.
          prune-cache: ${{ matrix.os != 'windows-latest' }}

      - name: Test Installation
        shell: bash
        run: |
          uv pip install .
          uv pip uninstall hikari

          uv pip install .[speedups]
          uv pip uninstall hikari

      - name: Run tests
        shell: bash
        run: |
          uv sync --frozen --only-group nox

          nox -s pytest -- --coverage
          nox -s pytest-all-features -- --coverage --cov-append

          python scripts/ci/normalize_coverage.py

      - name: Upload coverage
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: .coverage.${{ matrix.os }}.${{ matrix.python-version }}
          path: .coverage
          retention-days: 1
          if-no-files-found: error
          include-hidden-files: true

  upload-coverage:
    needs: [test]
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          python-version: 3.14
          activate-environment: true

      - name: Download coverage
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: coverages/
          # Not specifying any name will lead to the download of all available artifacts

      # Since artifacts v2, we can no longer upload multiple artifacts
      # with the same name and have them re-download as if it were a directory.
      # For this reason, we need to download all available artifacts, filter
      # out for only coverage files and then place them in the root directory
      # with the name of the artifact
      - name: Extract individual coverage files
        run: |
          cd coverages

          for coverage_dir in ./.coverage.*; do
              mv "$coverage_dir/.coverage" "../$coverage_dir"
              rmdir "$coverage_dir"
          done

          cd ..

      - name: Combine coverage
        run: |
          uv sync --frozen --only-group coverage

          coverage combine
          coverage xml
          coverage report

      - name: Upload coverage to codecov
        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0

  linting:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          python-version: "3.10"
          activate-environment: true

      - name: Install nox
        run: |
          uv sync --frozen --only-group nox

      - name: Check stubs
        if: always() && !cancelled()
        run: |
          nox -s generate-stubs
          if [ "$(git status --short)" ]; then
            echo "Stubs were not updated accordingly to the changes. Please run 'nox -s generate-stubs' and commit the changes to fix this."
            exit 1
          fi

      - name: Audit
        if: always() && !cancelled()
        run: |
          nox -s audit

      - name: Mypy
        if: always() && !cancelled()
        run: |
          nox -s mypy

      - name: Verify types
        if: always() && !cancelled()
        run: |
          nox -s verify-types

      - name: Ruff
        if: always() && !cancelled()
        run: |
          nox -s ruff -- --output-format=github

      - name: Slotscheck
        if: always() && !cancelled()
        run: |
          nox -s slotscheck

      - name: Codespell
        if: always() && !cancelled()
        run: |
          nox -s codespell

      - name: Check trailing whitespaces
        if: always() && !cancelled()
        run: |
          nox -s check-trailing-whitespaces

  twemoji:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          python-version: 3.14
          activate-environment: true

      - name: Test twemoji mapping
        run: |
          uv sync --frozen --only-group nox

          nox -s twemoji-test

  docs:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          # NOTE: This should be kept up to date with .readthedocs.yaml
          python-version: 3.13
          activate-environment: true

      - name: Build documentation
        run: |
          uv sync --frozen --only-group nox
          nox -s mkdocs

      - name: Upload artifacts
        if: github.event_name == 'pull_request'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docs
          path: public/docs
          retention-days: 2
          if-no-files-found: error

  # Allows us to add this as a required check in Github branch rules, as all the other jobs are subject to change
  ci-done:
    needs: [upload-coverage, linting, twemoji, docs]
    if: always() && !cancelled()

    runs-on: ubuntu-latest

    steps:
      - name: Set status based on required jobs
        env:
          RESULTS: ${{ join(needs.*.result, ' ') }}
        run: |
          for result in $RESULTS; do
            if [ "$result" != "success" ]; then
              exit 1
            fi
          done

The same workflow, on Latchkey

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

name: CI
 
on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  test:
    timeout-minutes: 30
    strategy:
      # Allows for matrix sub-jobs to fail without cancelling the rest
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.10", 3.11, 3.12, 3.13, 3.14]
 
    runs-on: ${{ matrix.os }}
 
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
 
      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          python-version: ${{ matrix.python-version }}
          activate-environment: true
          # TODO(https://github.com/astral-sh/setup-uv/issues/226): Remove this.
          prune-cache: ${{ matrix.os != 'windows-latest' }}
 
      - name: Test Installation
        shell: bash
        run: |
          uv pip install .
          uv pip uninstall hikari
 
          uv pip install .[speedups]
          uv pip uninstall hikari
 
      - name: Run tests
        shell: bash
        run: |
          uv sync --frozen --only-group nox
 
          nox -s pytest -- --coverage
          nox -s pytest-all-features -- --coverage --cov-append
 
          python scripts/ci/normalize_coverage.py
 
      - name: Upload coverage
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: .coverage.${{ matrix.os }}.${{ matrix.python-version }}
          path: .coverage
          retention-days: 1
          if-no-files-found: error
          include-hidden-files: true
 
  upload-coverage:
    timeout-minutes: 30
    needs: [test]
    runs-on: latchkey-small
 
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
 
      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          python-version: 3.14
          activate-environment: true
 
      - name: Download coverage
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: coverages/
          # Not specifying any name will lead to the download of all available artifacts
 
      # Since artifacts v2, we can no longer upload multiple artifacts
      # with the same name and have them re-download as if it were a directory.
      # For this reason, we need to download all available artifacts, filter
      # out for only coverage files and then place them in the root directory
      # with the name of the artifact
      - name: Extract individual coverage files
        run: |
          cd coverages
 
          for coverage_dir in ./.coverage.*; do
              mv "$coverage_dir/.coverage" "../$coverage_dir"
              rmdir "$coverage_dir"
          done
 
          cd ..
 
      - name: Combine coverage
        run: |
          uv sync --frozen --only-group coverage
 
          coverage combine
          coverage xml
          coverage report
 
      - name: Upload coverage to codecov
        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
 
  linting:
    timeout-minutes: 30
    runs-on: latchkey-small
 
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
 
      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          python-version: "3.10"
          activate-environment: true
 
      - name: Install nox
        run: |
          uv sync --frozen --only-group nox
 
      - name: Check stubs
        if: always() && !cancelled()
        run: |
          nox -s generate-stubs
          if [ "$(git status --short)" ]; then
            echo "Stubs were not updated accordingly to the changes. Please run 'nox -s generate-stubs' and commit the changes to fix this."
            exit 1
          fi
 
      - name: Audit
        if: always() && !cancelled()
        run: |
          nox -s audit
 
      - name: Mypy
        if: always() && !cancelled()
        run: |
          nox -s mypy
 
      - name: Verify types
        if: always() && !cancelled()
        run: |
          nox -s verify-types
 
      - name: Ruff
        if: always() && !cancelled()
        run: |
          nox -s ruff -- --output-format=github
 
      - name: Slotscheck
        if: always() && !cancelled()
        run: |
          nox -s slotscheck
 
      - name: Codespell
        if: always() && !cancelled()
        run: |
          nox -s codespell
 
      - name: Check trailing whitespaces
        if: always() && !cancelled()
        run: |
          nox -s check-trailing-whitespaces
 
  twemoji:
    timeout-minutes: 30
    runs-on: latchkey-small
 
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
 
      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          python-version: 3.14
          activate-environment: true
 
      - name: Test twemoji mapping
        run: |
          uv sync --frozen --only-group nox
 
          nox -s twemoji-test
 
  docs:
    timeout-minutes: 30
    runs-on: latchkey-small
 
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
 
      - name: Setup uv
        uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
        with:
          # NOTE: This should be kept up to date with .readthedocs.yaml
          python-version: 3.13
          activate-environment: true
 
      - name: Build documentation
        run: |
          uv sync --frozen --only-group nox
          nox -s mkdocs
 
      - name: Upload artifacts
        if: github.event_name == 'pull_request'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docs
          path: public/docs
          retention-days: 2
          if-no-files-found: error
 
  # Allows us to add this as a required check in Github branch rules, as all the other jobs are subject to change
  ci-done:
    timeout-minutes: 30
    needs: [upload-coverage, linting, twemoji, docs]
    if: always() && !cancelled()
 
    runs-on: latchkey-small
 
    steps:
      - name: Set status based on required jobs
        env:
          RESULTS: ${{ join(needs.*.result, ' ') }}
        run: |
          for result in $RESULTS; do
            if [ "$result" != "success" ]; then
              exit 1
            fi
          done
 

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 (20 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