Skip to content
Latchkey

CI workflow (affaan-m/ECC)

The CI workflow from affaan-m/ECC, explained and optimized by Latchkey.

A

CI health: A - excellent

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

Grade your own workflow free or run it on Latchkey →
Source: affaan-m/ECC.github/workflows/ci.ymlLicense MITView source

What it does

This is the CI workflow from the affaan-m/ECC 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, 'release/**']
    tags: ['v*']
  pull_request:
    branches: [main]

# Prevent duplicate runs
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

# Minimal permissions
permissions:
  contents: read

jobs:
  test:
    name: Test (${{ matrix.os }}, Node ${{ matrix.node }}, ${{ matrix.pm }})
    runs-on: ${{ matrix.os }}
    timeout-minutes: 10

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: ['18.x', '20.x', '22.x']
        pm: [npm, pnpm, yarn, bun]
        exclude:
          # Bun has limited Windows support
          - os: windows-latest
            pm: bun

    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Setup Node.js ${{ matrix.node }}
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.node }}

      # Package manager setup
      - name: Setup pnpm
        if: matrix.pm == 'pnpm' && matrix.node != '18.x'
        uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
        with:
          # Keep an explicit pnpm major because this repo's packageManager is Yarn.
          version: 10

      - name: Setup pnpm (via Corepack)
        if: matrix.pm == 'pnpm' && matrix.node == '18.x'
        shell: bash
        run: |
          corepack enable
          corepack prepare pnpm@9 --activate

      - name: Setup Yarn (via Corepack)
        if: matrix.pm == 'yarn'
        shell: bash
        run: |
          corepack enable
          corepack prepare yarn@stable --activate

      - name: Setup Bun
        if: matrix.pm == 'bun'
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2

      # Install dependencies
      # COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
      # package.json declares "packageManager": "yarn@..."
      - name: Install dependencies
        shell: bash
        env:
          COREPACK_ENABLE_STRICT: '0'
          npm_config_ignore_scripts: 'true'
          YARN_ENABLE_SCRIPTS: 'false'
        run: |
          case "${{ matrix.pm }}" in
            npm) npm ci --ignore-scripts ;;
            # pnpm v10 can fail CI on ignored native build scripts
            # (for example msgpackr-extract) even though this repo is Yarn-native
            # and pnpm is only exercised here as a compatibility lane.
            pnpm) pnpm install --ignore-scripts --config.strict-dep-builds=false --no-frozen-lockfile ;;
            # Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
            yarn) yarn install --mode=skip-build ;;
            bun) bun install --ignore-scripts ;;
            *) echo "Unsupported package manager: ${{ matrix.pm }}" && exit 1 ;;
          esac

      # Run tests
      - name: Run tests
        run: node tests/run-all.js
        env:
          CLAUDE_CODE_PACKAGE_MANAGER: ${{ matrix.pm }}

      # Upload test artifacts on failure
      - name: Upload test artifacts
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ matrix.pm }}
          path: |
            tests/
            !tests/node_modules/

  validate:
    name: Validate Components
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: '20.x'

      - name: Install validation dependencies
        run: npm ci --ignore-scripts

      - name: Validate agents
        run: node scripts/ci/validate-agents.js
        continue-on-error: false

      - name: Validate hooks
        run: node scripts/ci/validate-hooks.js
        continue-on-error: false

      - name: Validate commands
        run: node scripts/ci/validate-commands.js
        continue-on-error: false

      - name: Validate skills
        run: node scripts/ci/validate-skills.js
        continue-on-error: false

      - name: Validate install manifests
        run: node scripts/ci/validate-install-manifests.js
        continue-on-error: false

      - name: Validate workflow security
        run: node scripts/ci/validate-workflow-security.js
        continue-on-error: false

      - name: Validate rules
        run: node scripts/ci/validate-rules.js
        continue-on-error: false

      - name: Validate catalog counts
        run: node scripts/ci/catalog.js --text
        continue-on-error: false

      - name: Validate command registry
        run: npm run command-registry:check
        continue-on-error: false

      - name: Check unicode safety
        run: node scripts/ci/check-unicode-safety.js
        continue-on-error: false

      - name: Validate no personal paths
        run: node scripts/ci/validate-no-personal-paths.js
        continue-on-error: false

  python-tests:
    name: Python Tests
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Setup Python
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: '3.11'

      - name: Install Python dependencies
        run: python -m pip install --upgrade pip && python -m pip install -e '.[dev]'

      - name: Run Python tests
        run: python -m pytest tests/test_*.py -m "not integration"

  security:
    name: Security Scan
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: '20.x'

      - name: Install audit dependencies
        run: npm ci --ignore-scripts

      - name: Run npm audit
        run: |
          npm audit signatures
          npm audit --audit-level=high

      - name: Run supply-chain IOC scan
        run: npm run security:ioc-scan

  coverage:
    name: Coverage
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: '20.x'

      - name: Install dependencies
        run: npm ci --ignore-scripts

      - name: Run coverage
        run: npm run coverage

      - name: Upload coverage report
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: coverage-ubuntu-node20-npm
          path: coverage/

  lint:
    name: Lint
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: '20.x'

      - name: Install dependencies
        run: npm ci --ignore-scripts

      - name: Run lint
        run: npm run lint

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, 'release/**']
    tags: ['v*']
  pull_request:
    branches: [main]
 
# Prevent duplicate runs
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
# Minimal permissions
permissions:
  contents: read
 
jobs:
  test:
    name: Test (${{ matrix.os }}, Node ${{ matrix.node }}, ${{ matrix.pm }})
    runs-on: ${{ matrix.os }}
    timeout-minutes: 10
 
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: ['18.x', '20.x', '22.x']
        pm: [npm, pnpm, yarn, bun]
        exclude:
          # Bun has limited Windows support
          - os: windows-latest
            pm: bun
 
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
 
      - name: Setup Node.js ${{ matrix.node }}
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: ${{ matrix.node }}
 
      # Package manager setup
      - name: Setup pnpm
        if: matrix.pm == 'pnpm' && matrix.node != '18.x'
        uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
        with:
          # Keep an explicit pnpm major because this repo's packageManager is Yarn.
          version: 10
 
      - name: Setup pnpm (via Corepack)
        if: matrix.pm == 'pnpm' && matrix.node == '18.x'
        shell: bash
        run: |
          corepack enable
          corepack prepare pnpm@9 --activate
 
      - name: Setup Yarn (via Corepack)
        if: matrix.pm == 'yarn'
        shell: bash
        run: |
          corepack enable
          corepack prepare yarn@stable --activate
 
      - name: Setup Bun
        if: matrix.pm == 'bun'
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
 
      # Install dependencies
      # COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
      # package.json declares "packageManager": "yarn@..."
      - name: Install dependencies
        shell: bash
        env:
          COREPACK_ENABLE_STRICT: '0'
          npm_config_ignore_scripts: 'true'
          YARN_ENABLE_SCRIPTS: 'false'
        run: |
          case "${{ matrix.pm }}" in
            npm) npm ci --ignore-scripts ;;
            # pnpm v10 can fail CI on ignored native build scripts
            # (for example msgpackr-extract) even though this repo is Yarn-native
            # and pnpm is only exercised here as a compatibility lane.
            pnpm) pnpm install --ignore-scripts --config.strict-dep-builds=false --no-frozen-lockfile ;;
            # Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
            yarn) yarn install --mode=skip-build ;;
            bun) bun install --ignore-scripts ;;
            *) echo "Unsupported package manager: ${{ matrix.pm }}" && exit 1 ;;
          esac
 
      # Run tests
      - name: Run tests
        run: node tests/run-all.js
        env:
          CLAUDE_CODE_PACKAGE_MANAGER: ${{ matrix.pm }}
 
      # Upload test artifacts on failure
      - name: Upload test artifacts
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ matrix.pm }}
          path: |
            tests/
            !tests/node_modules/
 
  validate:
    name: Validate Components
    runs-on: latchkey-small
    timeout-minutes: 5
 
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
 
      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: '20.x'
 
      - name: Install validation dependencies
        run: npm ci --ignore-scripts
 
      - name: Validate agents
        run: node scripts/ci/validate-agents.js
        continue-on-error: false
 
      - name: Validate hooks
        run: node scripts/ci/validate-hooks.js
        continue-on-error: false
 
      - name: Validate commands
        run: node scripts/ci/validate-commands.js
        continue-on-error: false
 
      - name: Validate skills
        run: node scripts/ci/validate-skills.js
        continue-on-error: false
 
      - name: Validate install manifests
        run: node scripts/ci/validate-install-manifests.js
        continue-on-error: false
 
      - name: Validate workflow security
        run: node scripts/ci/validate-workflow-security.js
        continue-on-error: false
 
      - name: Validate rules
        run: node scripts/ci/validate-rules.js
        continue-on-error: false
 
      - name: Validate catalog counts
        run: node scripts/ci/catalog.js --text
        continue-on-error: false
 
      - name: Validate command registry
        run: npm run command-registry:check
        continue-on-error: false
 
      - name: Check unicode safety
        run: node scripts/ci/check-unicode-safety.js
        continue-on-error: false
 
      - name: Validate no personal paths
        run: node scripts/ci/validate-no-personal-paths.js
        continue-on-error: false
 
  python-tests:
    name: Python Tests
    runs-on: latchkey-small
    timeout-minutes: 10
 
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
 
      - name: Setup Python
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: '3.11'
 
      - name: Install Python dependencies
        run: python -m pip install --upgrade pip && python -m pip install -e '.[dev]'
 
      - name: Run Python tests
        run: python -m pytest tests/test_*.py -m "not integration"
 
  security:
    name: Security Scan
    runs-on: latchkey-small
    timeout-minutes: 5
 
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
 
      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: '20.x'
 
      - name: Install audit dependencies
        run: npm ci --ignore-scripts
 
      - name: Run npm audit
        run: |
          npm audit signatures
          npm audit --audit-level=high
 
      - name: Run supply-chain IOC scan
        run: npm run security:ioc-scan
 
  coverage:
    name: Coverage
    runs-on: latchkey-small
    timeout-minutes: 10
 
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
 
      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: '20.x'
 
      - name: Install dependencies
        run: npm ci --ignore-scripts
 
      - name: Run coverage
        run: npm run coverage
 
      - name: Upload coverage report
        if: always()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: coverage-ubuntu-node20-npm
          path: coverage/
 
  lint:
    name: Lint
    runs-on: latchkey-small
    timeout-minutes: 5
 
    steps:
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
 
      - name: Setup Node.js
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: '20.x'
 
      - name: Install dependencies
        run: npm ci --ignore-scripts
 
      - name: Run lint
        run: npm run lint
 

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