CI workflow (personalrobotics/ssik)
The CI workflow from personalrobotics/ssik, explained and optimized by Latchkey.
CI health: B - good
Point runs-on at Latchkey and get job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the CI workflow from the personalrobotics/ssik repository, a real project running GitHub Actions. It is shown here with attribution under its BSD-3-Clause license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: CI
# PR CI runs full pytest on Linux only. macOS coverage is provided by:
# * Local dev on macOS (the primary dev machine)
# * Release workflow's cibuildwheel matrix on tag (full cross-platform)
#
# Local pre-push gate (scripts/check.sh, auto-installed via
# scripts/install-hooks.sh) runs ruff + format + mypy + pytest locally
# before push. CI then runs Linux pytest as the safety net for the one
# class of bug local-only can't catch: Linux-specific behavior (LAPACK
# backend, libc/manylinux quirks, #82-style variance).
#
# Cost per PR: target ~5-10 min wall, free (public repo). The suite had
# grown to 43-68 min (serial pytest + uncached robot_descriptions clones);
# #348 cached the description repos and parallelized pytest (-n auto).
# Doc-only PRs skip CI entirely via paths-ignore.
on:
pull_request:
paths-ignore:
- "**.md"
- "docs/**"
- "LICENSE"
- ".gitignore"
push:
branches: [main]
paths-ignore:
- "**.md"
- "docs/**"
- "LICENSE"
- ".gitignore"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
test:
name: Linux py${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.13"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # hatch-vcs needs git history
- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Sync dependencies
run: uv sync --dev --python ${{ matrix.python-version }}
- name: Runner info
# Surfaces the runner's real core count up front (a standalone step so it
# logs immediately, unlike a line inside the long pytest step). Confirms
# whether xdist parallelism can help here. (#348)
run: |
echo "nproc=$(nproc)"
python3 -c "import os; print('cpu_count', os.cpu_count(), 'affinity', len(os.sched_getaffinity(0)))"
# Cache the robot_descriptions clones (~16 repos, several with mesh
# files). Uncached, these clones dominate CI wall-clock (~30-55 min) and
# cause the run-to-run variance. Keyed on MANIFEST.toml so adding an arm
# invalidates; restore-keys lets a manifest change reuse the prior cache
# so the prewarm step only clones the new entry. (#348)
- name: Cache robot_descriptions
uses: actions/cache@v4
with:
path: ~/.cache/robot_descriptions
key: rd-${{ hashFiles('src/ssik/prebuilt/MANIFEST.toml') }}
restore-keys: rd-
- name: Pre-warm robot_descriptions
# Serial clone before pytest so the parallel (-n auto) workers only
# read the cache -- no concurrent clones racing on the same dir.
run: uv run python scripts/prewarm_descriptions.py
- name: ruff check
run: uv run ruff check
- name: ruff format --check
run: uv run ruff format --check
- name: mypy
run: uv run mypy
- name: regen_docs --check
# Fail if MANIFEST.toml and the AUTOGEN doc-table regions are
# out of sync. The script regenerates anchored tables from
# the manifest; this gate enforces "edit MANIFEST.toml + run
# scripts/regen_docs.py" as the only correct workflow for
# arm metadata changes.
run: uv run python scripts/regen_docs.py --check
- name: pytest (parallel)
# -n 4 (ubuntu-latest = 4 vCPUs). The heavy redundant tests (full
# snapshot roster, etc.) are marked slow and run on the macOS pre-push
# hook + nightly, so the per-PR suite is lean. Descriptions are
# pre-warmed above so workers never clone concurrently. perf gates are
# excluded here and run serially below. (#348)
run: uv run pytest -q -n 4 -m "not slow and not perf"
- name: pytest (perf gates, serial)
# Wall-clock performance gates: run serially (no -n) so timing is
# measured without cross-worker CPU contention. Small, fast set. (#348)
run: uv run pytest -q -m "not slow and perf"
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: CI # PR CI runs full pytest on Linux only. macOS coverage is provided by: # * Local dev on macOS (the primary dev machine) # * Release workflow's cibuildwheel matrix on tag (full cross-platform) # # Local pre-push gate (scripts/check.sh, auto-installed via # scripts/install-hooks.sh) runs ruff + format + mypy + pytest locally # before push. CI then runs Linux pytest as the safety net for the one # class of bug local-only can't catch: Linux-specific behavior (LAPACK # backend, libc/manylinux quirks, #82-style variance). # # Cost per PR: target ~5-10 min wall, free (public repo). The suite had # grown to 43-68 min (serial pytest + uncached robot_descriptions clones); # #348 cached the description repos and parallelized pytest (-n auto). # Doc-only PRs skip CI entirely via paths-ignore. on: pull_request: paths-ignore: - "**.md" - "docs/**" - "LICENSE" - ".gitignore" push: branches: [main] paths-ignore: - "**.md" - "docs/**" - "LICENSE" - ".gitignore" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: test: timeout-minutes: 30 name: Linux py${{ matrix.python-version }} runs-on: latchkey-small strategy: fail-fast: false matrix: python-version: ["3.13"] steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # hatch-vcs needs git history - name: Set up uv uses: astral-sh/setup-uv@v4 with: enable-cache: true cache-dependency-glob: "uv.lock" - name: Sync dependencies run: uv sync --dev --python ${{ matrix.python-version }} - name: Runner info # Surfaces the runner's real core count up front (a standalone step so it # logs immediately, unlike a line inside the long pytest step). Confirms # whether xdist parallelism can help here. (#348) run: | echo "nproc=$(nproc)" python3 -c "import os; print('cpu_count', os.cpu_count(), 'affinity', len(os.sched_getaffinity(0)))" # Cache the robot_descriptions clones (~16 repos, several with mesh # files). Uncached, these clones dominate CI wall-clock (~30-55 min) and # cause the run-to-run variance. Keyed on MANIFEST.toml so adding an arm # invalidates; restore-keys lets a manifest change reuse the prior cache # so the prewarm step only clones the new entry. (#348) - name: Cache robot_descriptions uses: actions/cache@v4 with: path: ~/.cache/robot_descriptions key: rd-${{ hashFiles('src/ssik/prebuilt/MANIFEST.toml') }} restore-keys: rd- - name: Pre-warm robot_descriptions # Serial clone before pytest so the parallel (-n auto) workers only # read the cache -- no concurrent clones racing on the same dir. run: uv run python scripts/prewarm_descriptions.py - name: ruff check run: uv run ruff check - name: ruff format --check run: uv run ruff format --check - name: mypy run: uv run mypy - name: regen_docs --check # Fail if MANIFEST.toml and the AUTOGEN doc-table regions are # out of sync. The script regenerates anchored tables from # the manifest; this gate enforces "edit MANIFEST.toml + run # scripts/regen_docs.py" as the only correct workflow for # arm metadata changes. run: uv run python scripts/regen_docs.py --check - name: pytest (parallel) # -n 4 (ubuntu-latest = 4 vCPUs). The heavy redundant tests (full # snapshot roster, etc.) are marked slow and run on the macOS pre-push # hook + nightly, so the per-PR suite is lean. Descriptions are # pre-warmed above so workers never clone concurrently. perf gates are # excluded here and run serially below. (#348) run: uv run pytest -q -n 4 -m "not slow and not perf" - name: pytest (perf gates, serial) # Wall-clock performance gates: run serially (no -n) so timing is # measured without cross-worker CPU contention. Small, fast set. (#348) run: uv run pytest -q -m "not slow and perf"
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
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.
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.