Skip to content
Latchkey

CI workflow (nrwl/nx)

The CI workflow from nrwl/nx, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get run de-duplication, 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: nrwl/nx.github/workflows/ci.ymlLicense MITView source

What it does

This is the CI workflow from the nrwl/nx 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
      - '[0-9]+.[0-9]+.x'
  pull_request:
    branches:
      - "**"

env:
  NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
  NX_CLOUD_ENABLE_METRICS_COLLECTION: 'true'
  PNPM_HOME: ~/.pnpm
  # Pin corepack to the pnpm version from packageManager. Without this, corepack
  # falls back to "latest" in directories that have no packageManager field
  # (e.g. e2e temp dirs), pulling pnpm 11 and breaking install.
  COREPACK_DEFAULT_TO_LATEST: '0'

jobs:
  main-linux:
    runs-on: ubuntu-latest
    env:
      NX_BATCH_MODE: 'true'
      NX_E2E_CI_CACHE_KEY: e2e-github-linux
      NX_DAEMON: 'true'
      NX_PERF_LOGGING: 'false'
      NX_VERBOSE_LOGGING: 'false'
      NX_NATIVE_LOGGING: 'false'
      NX_E2E_RUN_E2E: 'true'
      NX_CI_EXECUTION_ENV: 'linux'
      NX_CLOUD_NO_TIMEOUTS: 'true'
      NX_ALLOW_NON_CACHEABLE_DTE: 'true'
      NX_CLOUD_EXPERIMENTAL_POLLING: 'true'
      NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'true'
      NX_CLOUD_VERBOSE_LOGGING: 'true'

    steps:
      - name: Checkout
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          fetch-depth: 0
          filter: tree:0

      - name: Set verbose logging from debug mode
        if: runner.debug == '1'
        run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"

      - name: Fetch Master
        run: git fetch origin master:master
        if: ${{ github.event_name == 'pull_request' }}

      - name: Set SHAs
        uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
        with:
          main-branch-name: 'master'

      - name: Start CI Run
        run: npx nx-cloud@next start-ci-run --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"

      - name: Setup dev tools with mise
        uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3

      - name: Enable corepack and install pnpm
        run: |
          corepack enable
          corepack prepare --activate

      - name: Get pnpm store directory
        id: pnpm-cache
        run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

      - name: Cache pnpm store
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@48b5f213c81028ace310571dc5ec0fbbca0b2947 # v4.4.3

      - name: Install project dependencies
        run: pnpm install --frozen-lockfile

      - name: Restore .NET analyzer projects
        run: dotnet restore nx.sln

      - name: Nx Report
        run:
          pnpm nx report

      - name: Run Checks/Lint/Test/Build
        run: |
          pids=()

          pnpm nx record -- nx format:check &
          pids+=($!)

          pnpm nx record -- nx sync:check
          pids+=($!)

          pnpm nx build workspace-plugin && pnpm nx record -- pnpm nx-cloud conformance:check
          pids+=($!)

          pnpm nx run-many -t check-imports check-lock-files check-codeowners --parallel=1 --no-dte &
          pids+=($!)

          pnpm nx affected --targets=lint,test,build,e2e,e2e-ci,format-native,lint-native,gradle:build-ci,vale,run &
          pids+=($!)

          for pid in "${pids[@]}"; do
            wait "$pid"
          done
        timeout-minutes: 100
      - name: Fix CI
        run: pnpm nx fix-ci
        if: failure()

  main-macos:
    runs-on: macos-latest

    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}
      cancel-in-progress: true

    env:
      NX_E2E_CI_CACHE_KEY: e2e-github-macos
      NX_PERF_LOGGING: 'false'
      NX_CI_EXECUTION_ENV: 'macos'
      SELECTED_PM: 'npm'
    steps:

      - name: Log concurrency info
        run: |
          echo "Concurrency group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}"
          echo "Concurrency cancel-in-progress: ${{ !contains(github.event_name, 'push') }}"
          echo "Concurrency cancel-event-name: ${{ github.event_name }}"
        if: always()

      - name: Checkout
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          fetch-depth: 0
          filter: tree:0

      - name: Set verbose logging from debug mode
        if: runner.debug == '1'
        run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"

      - name: Fetch Master
        run: git fetch origin master:master
        if: ${{ github.event_name == 'pull_request' }}

      - name: Setup dev tools with mise
        uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3

      - name: Enable corepack and install pnpm
        run: |
          corepack enable
          corepack prepare --activate

      - name: Set SHAs
        uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
        with:
          main-branch-name: 'master'

      - name: Check for React Native changes
        id: check-changes
        run: |
          HAS_CHANGED=$(node ./scripts/check-react-native-changes.js $NX_BASE $NX_HEAD);
          if $HAS_CHANGED; then
            echo "has_changes=true" >> $GITHUB_OUTPUT
            echo "React Native projects are affected, will run macOS tests"
          else
            echo "has_changes=false" >> $GITHUB_OUTPUT
            echo "No React Native projects affected, skipping macOS tests"
          fi

      - name: Restore Homebrew packages
        if: steps.check-changes.outputs.has_changes == 'true'
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: |
            /opt/homebrew
            ~/Library/Caches/Homebrew
          key: nrwl-nx-homebrew-packages

      - name: Configure Detox Environment, Install applesimutils
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          # Ensure Xcode command line tools are installed and configured
          xcode-select --print-path || sudo xcode-select --reset
          sudo xcode-select -s /Applications/Xcode.app

          # Install or update applesimutils with error handling
          if ! brew list applesimutils &>/dev/null; then
            echo "Installing applesimutils..."
            HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
            # Homebrew now refuses to load formulae from third-party taps unless trusted
            brew trust wix/brew
            HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null || {
              echo "Failed to install applesimutils, retrying with update..."
              brew update
              brew trust wix/brew
              HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils
            }
          else
            echo "Updating applesimutils..."
            HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade applesimutils || true
          fi

          # Verify applesimutils installation
          applesimutils --version || (echo "applesimutils installation failed" && exit 1)

          # Configure environment for M-series Mac
          echo "DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer" >> $GITHUB_ENV
          echo "PLATFORM_NAME=iOS Simulator" >> $GITHUB_ENV

          # Set additional environment variables for better debugging
          echo "DETOX_DISABLE_TELEMETRY=1" >> $GITHUB_ENV
          echo "DETOX_LOG_LEVEL=trace" >> $GITHUB_ENV

          # Verify Xcode installation
          xcodebuild -version

          # List available simulators
          xcrun simctl list devices available
        timeout-minutes: 10
        continue-on-error: false

      - name: Reset iOS Simulators
        if: steps.check-changes.outputs.has_changes == 'true'
        id: reset-simulators
        run: |
          echo "Resetting iOS Simulators..."

          # Kill simulator processes
          sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
          killall "Simulator" 2>/dev/null || true
          killall "iOS Simulator" 2>/dev/null || true

          # Wait for processes to terminate
          sleep 3

          # Shutdown and erase all simulators (ignore failures)
          xcrun simctl shutdown all 2>/dev/null || true
          sleep 5
          xcrun simctl erase all 2>/dev/null || true

          # If erase failed, try the nuclear option
          if xcrun simctl list devices | grep -q "Booted" 2>/dev/null; then
            echo "Standard reset failed, using nuclear option..."
            rm -rf ~/Library/Developer/CoreSimulator/Devices/* 2>/dev/null || true
            launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
            sleep 3
          fi

          # Clean up additional directories
          rm -rf ~/Library/Developer/CoreSimulator/Caches/* 2>/dev/null || true
          rm -rf ~/Library/Logs/CoreSimulator/* 2>/dev/null || true
          rm -rf ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null || true

          echo "Simulator reset completed"
        timeout-minutes: 5
        continue-on-error: true

      - name: Verify Simulator Reset
        if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'success'
        run: |
          # Verify CoreSimulator service restarted
          pgrep -fl "CoreSimulator" || (echo "CoreSimulator service not running" && exit 1)

          # Check simulator list is clean
          xcrun simctl list devices

          # Verify simulator runtime paths exist and are writable
          test -d ~/Library/Developer/CoreSimulator/Devices || (echo "Simulator devices directory missing" && exit 1)
          touch ~/Library/Developer/CoreSimulator/Devices/test || (echo "Simulator devices directory not writable" && exit 1)
          rm ~/Library/Developer/CoreSimulator/Devices/test
        timeout-minutes: 5

      - name: Diagnose Simulator Reset Failure
        if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'failure'
        run: |
          echo "Simulator reset failed. Collecting diagnostic information..."
          xcrun simctl list
          echo "Checking simulator logs..."
          ls -la ~/Library/Logs/CoreSimulator/ || echo "No simulator logs found"

      - name: Save Homebrew Cache
        if: steps.check-changes.outputs.has_changes == 'true'
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: |
            /opt/homebrew
            ~/Library/Caches/Homebrew
          key: nrwl-nx-homebrew-packages

      - name: Get pnpm store directory
        if: steps.check-changes.outputs.has_changes == 'true'
        id: pnpm-cache-macos
        run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

      - name: Cache pnpm store
        if: steps.check-changes.outputs.has_changes == 'true'
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: ${{ steps.pnpm-cache-macos.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install project dependencies
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          pnpm install --frozen-lockfile
          pnpm playwright install --with-deps

      - name: Restore .NET packages
        if: steps.check-changes.outputs.has_changes == 'true'
        run: dotnet restore nx.sln

      - name: Run E2E Tests for macOS
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          pnpm nx affected -t e2e-macos-local --parallel=1 --base=$NX_BASE --head=$NX_HEAD

The same workflow, on Latchkey

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

name: CI
 
on:
  push:
    branches:
      - master
      - '[0-9]+.[0-9]+.x'
  pull_request:
    branches:
      - "**"
 
env:
  NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
  NX_CLOUD_ENABLE_METRICS_COLLECTION: 'true'
  PNPM_HOME: ~/.pnpm
  # Pin corepack to the pnpm version from packageManager. Without this, corepack
  # falls back to "latest" in directories that have no packageManager field
  # (e.g. e2e temp dirs), pulling pnpm 11 and breaking install.
  COREPACK_DEFAULT_TO_LATEST: '0'
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  main-linux:
    timeout-minutes: 30
    runs-on: latchkey-small
    env:
      NX_BATCH_MODE: 'true'
      NX_E2E_CI_CACHE_KEY: e2e-github-linux
      NX_DAEMON: 'true'
      NX_PERF_LOGGING: 'false'
      NX_VERBOSE_LOGGING: 'false'
      NX_NATIVE_LOGGING: 'false'
      NX_E2E_RUN_E2E: 'true'
      NX_CI_EXECUTION_ENV: 'linux'
      NX_CLOUD_NO_TIMEOUTS: 'true'
      NX_ALLOW_NON_CACHEABLE_DTE: 'true'
      NX_CLOUD_EXPERIMENTAL_POLLING: 'true'
      NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'true'
      NX_CLOUD_VERBOSE_LOGGING: 'true'
 
    steps:
      - name: Checkout
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          fetch-depth: 0
          filter: tree:0
 
      - name: Set verbose logging from debug mode
        if: runner.debug == '1'
        run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
 
      - name: Fetch Master
        run: git fetch origin master:master
        if: ${{ github.event_name == 'pull_request' }}
 
      - name: Set SHAs
        uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
        with:
          main-branch-name: 'master'
 
      - name: Start CI Run
        run: npx nx-cloud@next start-ci-run --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"
 
      - name: Setup dev tools with mise
        uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
 
      - name: Enable corepack and install pnpm
        run: |
          corepack enable
          corepack prepare --activate
 
      - name: Get pnpm store directory
        id: pnpm-cache
        run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
 
      - name: Cache pnpm store
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-
 
      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@48b5f213c81028ace310571dc5ec0fbbca0b2947 # v4.4.3
 
      - name: Install project dependencies
        run: pnpm install --frozen-lockfile
 
      - name: Restore .NET analyzer projects
        run: dotnet restore nx.sln
 
      - name: Nx Report
        run:
          pnpm nx report
 
      - name: Run Checks/Lint/Test/Build
        run: |
          pids=()
 
          pnpm nx record -- nx format:check &
          pids+=($!)
 
          pnpm nx record -- nx sync:check
          pids+=($!)
 
          pnpm nx build workspace-plugin && pnpm nx record -- pnpm nx-cloud conformance:check
          pids+=($!)
 
          pnpm nx run-many -t check-imports check-lock-files check-codeowners --parallel=1 --no-dte &
          pids+=($!)
 
          pnpm nx affected --targets=lint,test,build,e2e,e2e-ci,format-native,lint-native,gradle:build-ci,vale,run &
          pids+=($!)
 
          for pid in "${pids[@]}"; do
            wait "$pid"
          done
        timeout-minutes: 100
      - name: Fix CI
        run: pnpm nx fix-ci
        if: failure()
 
  main-macos:
    timeout-minutes: 30
    runs-on: macos-latest
 
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}
      cancel-in-progress: true
 
    env:
      NX_E2E_CI_CACHE_KEY: e2e-github-macos
      NX_PERF_LOGGING: 'false'
      NX_CI_EXECUTION_ENV: 'macos'
      SELECTED_PM: 'npm'
    steps:
 
      - name: Log concurrency info
        run: |
          echo "Concurrency group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}"
          echo "Concurrency cancel-in-progress: ${{ !contains(github.event_name, 'push') }}"
          echo "Concurrency cancel-event-name: ${{ github.event_name }}"
        if: always()
 
      - name: Checkout
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          fetch-depth: 0
          filter: tree:0
 
      - name: Set verbose logging from debug mode
        if: runner.debug == '1'
        run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
 
      - name: Fetch Master
        run: git fetch origin master:master
        if: ${{ github.event_name == 'pull_request' }}
 
      - name: Setup dev tools with mise
        uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
 
      - name: Enable corepack and install pnpm
        run: |
          corepack enable
          corepack prepare --activate
 
      - name: Set SHAs
        uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
        with:
          main-branch-name: 'master'
 
      - name: Check for React Native changes
        id: check-changes
        run: |
          HAS_CHANGED=$(node ./scripts/check-react-native-changes.js $NX_BASE $NX_HEAD);
          if $HAS_CHANGED; then
            echo "has_changes=true" >> $GITHUB_OUTPUT
            echo "React Native projects are affected, will run macOS tests"
          else
            echo "has_changes=false" >> $GITHUB_OUTPUT
            echo "No React Native projects affected, skipping macOS tests"
          fi
 
      - name: Restore Homebrew packages
        if: steps.check-changes.outputs.has_changes == 'true'
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: |
            /opt/homebrew
            ~/Library/Caches/Homebrew
          key: nrwl-nx-homebrew-packages
 
      - name: Configure Detox Environment, Install applesimutils
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          # Ensure Xcode command line tools are installed and configured
          xcode-select --print-path || sudo xcode-select --reset
          sudo xcode-select -s /Applications/Xcode.app
 
          # Install or update applesimutils with error handling
          if ! brew list applesimutils &>/dev/null; then
            echo "Installing applesimutils..."
            HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
            # Homebrew now refuses to load formulae from third-party taps unless trusted
            brew trust wix/brew
            HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null || {
              echo "Failed to install applesimutils, retrying with update..."
              brew update
              brew trust wix/brew
              HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils
            }
          else
            echo "Updating applesimutils..."
            HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade applesimutils || true
          fi
 
          # Verify applesimutils installation
          applesimutils --version || (echo "applesimutils installation failed" && exit 1)
 
          # Configure environment for M-series Mac
          echo "DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer" >> $GITHUB_ENV
          echo "PLATFORM_NAME=iOS Simulator" >> $GITHUB_ENV
 
          # Set additional environment variables for better debugging
          echo "DETOX_DISABLE_TELEMETRY=1" >> $GITHUB_ENV
          echo "DETOX_LOG_LEVEL=trace" >> $GITHUB_ENV
 
          # Verify Xcode installation
          xcodebuild -version
 
          # List available simulators
          xcrun simctl list devices available
        timeout-minutes: 10
        continue-on-error: false
 
      - name: Reset iOS Simulators
        if: steps.check-changes.outputs.has_changes == 'true'
        id: reset-simulators
        run: |
          echo "Resetting iOS Simulators..."
 
          # Kill simulator processes
          sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
          killall "Simulator" 2>/dev/null || true
          killall "iOS Simulator" 2>/dev/null || true
 
          # Wait for processes to terminate
          sleep 3
 
          # Shutdown and erase all simulators (ignore failures)
          xcrun simctl shutdown all 2>/dev/null || true
          sleep 5
          xcrun simctl erase all 2>/dev/null || true
 
          # If erase failed, try the nuclear option
          if xcrun simctl list devices | grep -q "Booted" 2>/dev/null; then
            echo "Standard reset failed, using nuclear option..."
            rm -rf ~/Library/Developer/CoreSimulator/Devices/* 2>/dev/null || true
            launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
            sleep 3
          fi
 
          # Clean up additional directories
          rm -rf ~/Library/Developer/CoreSimulator/Caches/* 2>/dev/null || true
          rm -rf ~/Library/Logs/CoreSimulator/* 2>/dev/null || true
          rm -rf ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null || true
 
          echo "Simulator reset completed"
        timeout-minutes: 5
        continue-on-error: true
 
      - name: Verify Simulator Reset
        if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'success'
        run: |
          # Verify CoreSimulator service restarted
          pgrep -fl "CoreSimulator" || (echo "CoreSimulator service not running" && exit 1)
 
          # Check simulator list is clean
          xcrun simctl list devices
 
          # Verify simulator runtime paths exist and are writable
          test -d ~/Library/Developer/CoreSimulator/Devices || (echo "Simulator devices directory missing" && exit 1)
          touch ~/Library/Developer/CoreSimulator/Devices/test || (echo "Simulator devices directory not writable" && exit 1)
          rm ~/Library/Developer/CoreSimulator/Devices/test
        timeout-minutes: 5
 
      - name: Diagnose Simulator Reset Failure
        if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'failure'
        run: |
          echo "Simulator reset failed. Collecting diagnostic information..."
          xcrun simctl list
          echo "Checking simulator logs..."
          ls -la ~/Library/Logs/CoreSimulator/ || echo "No simulator logs found"
 
      - name: Save Homebrew Cache
        if: steps.check-changes.outputs.has_changes == 'true'
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: |
            /opt/homebrew
            ~/Library/Caches/Homebrew
          key: nrwl-nx-homebrew-packages
 
      - name: Get pnpm store directory
        if: steps.check-changes.outputs.has_changes == 'true'
        id: pnpm-cache-macos
        run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
 
      - name: Cache pnpm store
        if: steps.check-changes.outputs.has_changes == 'true'
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: ${{ steps.pnpm-cache-macos.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-
 
      - name: Install project dependencies
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          pnpm install --frozen-lockfile
          pnpm playwright install --with-deps
 
      - name: Restore .NET packages
        if: steps.check-changes.outputs.has_changes == 'true'
        run: dotnet restore nx.sln
 
      - name: Run E2E Tests for macOS
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          pnpm nx affected -t e2e-macos-local --parallel=1 --base=$NX_BASE --head=$NX_HEAD
 

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 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow