Publish to PyPI workflow (avibe-bot/avibe)
The Publish to PyPI workflow from avibe-bot/avibe, explained and optimized by Latchkey.
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.
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
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
- 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. - Cancel superseded runs when a branch or PR gets a newer push.
- 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.
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:
- Dependency installs
- End-to-end and browser tests
This workflow runs 6 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.