Skip to content
Latchkey

Continuous Integration workflow (jesseduffield/lazygit)

The Continuous Integration workflow from jesseduffield/lazygit, 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: jesseduffield/lazygit.github/workflows/ci.ymlLicense MITView source

What it does

This is the Continuous Integration workflow from the jesseduffield/lazygit 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: Continuous Integration

env:
  GO_VERSION: 1.25

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  unit-tests:
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu-latest
          - windows-latest
        include:
          - os: ubuntu-latest
            cache_path: ~/.cache/go-build
          - os: windows-latest
            cache_path: ~\AppData\Local\go-build
    name: ci - ${{matrix.os}}
    runs-on: ${{matrix.os}}
    env:
      GOFLAGS: -mod=vendor
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Test code
        # we're passing -short so that we skip the integration tests, which will be run in parallel below
        run: |
          mkdir -p /tmp/code_coverage
          go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage"
      - name: Upload code coverage artifacts
        uses: actions/upload-artifact@v7
        with:
          name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
          path: /tmp/code_coverage

  integration-tests:
    strategy:
      fail-fast: false
      matrix:
        git-version:
          - 2.32.0 # oldest supported version
          - 2.38.2 # first version that supports the rebase.updateRefs config
          - 2.44.0
          - latest # We rely on github to have the latest version installed on their VMs
    runs-on: ubuntu-latest
    name: "Integration Tests - git ${{matrix.git-version}}"
    env:
      GOFLAGS: -mod=vendor
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Restore Git cache
        if: matrix.git-version != 'latest'
        id: cache-git-restore
        uses: actions/cache/restore@v6
        with:
          path: ~/git-${{matrix.git-version}}
          key: ${{runner.os}}-git-${{matrix.git-version}}
      - name: Build Git ${{matrix.git-version}}
        if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
        run: >
          sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential ca-certificates curl gettext libexpat1-dev libssl-dev libz-dev openssl
          && curl -sL "https://mirrors.edge.kernel.org/pub/software/scm/git/git-${{matrix.git-version}}.tar.xz" -o - | tar xJ -C "$HOME"
          && cd "$HOME/git-${{matrix.git-version}}"
          && ./configure
          && make -j
      - name: Install Git ${{matrix.git-version}}
        if: matrix.git-version != 'latest'
        run: sudo make -C "$HOME/git-${{matrix.git-version}}" -j install
      - name: Save Git cache
        if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
        uses: actions/cache/save@v6
        with:
          path: ~/git-${{matrix.git-version}}
          key: ${{runner.os}}-git-${{matrix.git-version}}
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Print git version
        run: git --version
      - name: Test code
        env:
          # See https://go.dev/blog/integration-test-coverage
          LAZYGIT_GOCOVERDIR: /tmp/code_coverage
        run: |
          mkdir -p /tmp/code_coverage
          ./scripts/run_integration_tests.sh
      - name: Upload code coverage artifacts
        uses: actions/upload-artifact@v7
        with:
          name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
          path: /tmp/code_coverage
  build:
    runs-on: ubuntu-latest
    env:
      GOFLAGS: -mod=vendor
      GOARCH: amd64
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Build linux binary
        run: |
          GOOS=linux go build
      - name: Build windows binary
        run: |
          GOOS=windows go build
      - name: Build darwin binary
        run: |
          GOOS=darwin go build
      - name: Build integration test binary
        run: |
          GOOS=linux go build cmd/integration_test/main.go
      - name: Build integration test injector
        run: |
          GOOS=linux go build pkg/integration/clients/injector/main.go
  check-codebase:
    runs-on: ubuntu-latest
    env:
      GOFLAGS: -mod=vendor
      GOARCH: amd64
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Check Vendor Directory
        # ensure our vendor directory matches up with our go modules
        run: |
          go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1)
      - name: Check go.mod file
        # ensure our go.mod file is clean
        run: |
          go mod tidy && git diff --exit-code || (echo "go.mod file is not clean. Run 'go mod tidy' locally and commit the changes" && exit 1)
      - name: Check All Auto-Generated Files
        # ensure all our auto-generated files are up to date
        run: |
          go generate ./... && git diff --quiet || (git status -s; echo "Auto-generated files not up to date. Run 'go generate ./...' locally and commit the changes" && exit 1)
        shell: bash # needed so that we get "-o pipefail"
      - name: Check Filenames
        run: scripts/check_filenames.sh
  lint:
    runs-on: ubuntu-latest
    env:
      GOFLAGS: -mod=vendor
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Check formatting
        run: ./scripts/gofumpt-check.sh
      - name: Lint
        # Run even if the formatting check failed, so that both sets of
        # problems are reported in a single CI run.
        if: ${{ !cancelled() }}
        uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
        with:
          # If you change this, make sure to also update scripts/golangci-lint-shim.sh
          version: v2.4.0
  upload-coverage:
    # List all jobs that produce coverage files
    needs: [unit-tests, integration-tests]
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v7

      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x

      - name: Download all coverage artifacts
        uses: actions/download-artifact@v8
        with:
          path: /tmp/code_coverage

      - name: Combine coverage files
        run: |
          # Find all directories in /tmp/code_coverage and create a comma-separated list
          COVERAGE_DIRS=$(find /tmp/code_coverage -mindepth 1 -maxdepth 1 -type d -printf '/tmp/code_coverage/%f,' | sed 's/,$//')
          echo "Coverage directories: $COVERAGE_DIRS"
          # Run the combine command with the generated list
          go tool covdata textfmt -i=$COVERAGE_DIRS -o coverage.out
          echo "Combined coverage:"
          go tool cover -func coverage.out | tail -1 | awk '{print $3}'

      - name: Upload to Codacy
        run: |
          CODACY_PROJECT_TOKEN="${CODACY_PROJECT_TOKEN}" \
            bash <(curl -Ls https://coverage.codacy.com/get.sh) report \
            --force-coverage-parser go -r coverage.out

        env:
          CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
  check-for-fixups:
    runs-on: ubuntu-latest
    if: github.ref != 'refs/heads/master'
    steps:
      # See https://github.com/actions/checkout/issues/552#issuecomment-1167086216
      - name: "PR commits"
        run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"

      - name: "Checkout PR branch and all PR commits"
        uses: actions/checkout@v7
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: ${{ env.PR_FETCH_DEPTH }}

      - name: Check for fixups
        run: |
          ./scripts/check_for_fixups.sh ${{ github.event.pull_request.base.ref }}

The same workflow, on Latchkey

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

name: Continuous Integration
 
env:
  GO_VERSION: 1.25
 
on:
  push:
    branches:
      - master
  pull_request:
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  unit-tests:
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu-latest
          - windows-latest
        include:
          - os: ubuntu-latest
            cache_path: ~/.cache/go-build
          - os: windows-latest
            cache_path: ~\AppData\Local\go-build
    name: ci - ${{matrix.os}}
    runs-on: ${{matrix.os}}
    env:
      GOFLAGS: -mod=vendor
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Test code
        # we're passing -short so that we skip the integration tests, which will be run in parallel below
        run: |
          mkdir -p /tmp/code_coverage
          go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage"
      - name: Upload code coverage artifacts
        uses: actions/upload-artifact@v7
        with:
          name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
          path: /tmp/code_coverage
 
  integration-tests:
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        git-version:
          - 2.32.0 # oldest supported version
          - 2.38.2 # first version that supports the rebase.updateRefs config
          - 2.44.0
          - latest # We rely on github to have the latest version installed on their VMs
    runs-on: latchkey-small
    name: "Integration Tests - git ${{matrix.git-version}}"
    env:
      GOFLAGS: -mod=vendor
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Restore Git cache
        if: matrix.git-version != 'latest'
        id: cache-git-restore
        uses: actions/cache/restore@v6
        with:
          path: ~/git-${{matrix.git-version}}
          key: ${{runner.os}}-git-${{matrix.git-version}}
      - name: Build Git ${{matrix.git-version}}
        if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
        run: >
          sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential ca-certificates curl gettext libexpat1-dev libssl-dev libz-dev openssl
          && curl -sL "https://mirrors.edge.kernel.org/pub/software/scm/git/git-${{matrix.git-version}}.tar.xz" -o - | tar xJ -C "$HOME"
          && cd "$HOME/git-${{matrix.git-version}}"
          && ./configure
          && make -j
      - name: Install Git ${{matrix.git-version}}
        if: matrix.git-version != 'latest'
        run: sudo make -C "$HOME/git-${{matrix.git-version}}" -j install
      - name: Save Git cache
        if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
        uses: actions/cache/save@v6
        with:
          path: ~/git-${{matrix.git-version}}
          key: ${{runner.os}}-git-${{matrix.git-version}}
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Print git version
        run: git --version
      - name: Test code
        env:
          # See https://go.dev/blog/integration-test-coverage
          LAZYGIT_GOCOVERDIR: /tmp/code_coverage
        run: |
          mkdir -p /tmp/code_coverage
          ./scripts/run_integration_tests.sh
      - name: Upload code coverage artifacts
        uses: actions/upload-artifact@v7
        with:
          name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
          path: /tmp/code_coverage
  build:
    timeout-minutes: 30
    runs-on: latchkey-small
    env:
      GOFLAGS: -mod=vendor
      GOARCH: amd64
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Build linux binary
        run: |
          GOOS=linux go build
      - name: Build windows binary
        run: |
          GOOS=windows go build
      - name: Build darwin binary
        run: |
          GOOS=darwin go build
      - name: Build integration test binary
        run: |
          GOOS=linux go build cmd/integration_test/main.go
      - name: Build integration test injector
        run: |
          GOOS=linux go build pkg/integration/clients/injector/main.go
  check-codebase:
    timeout-minutes: 30
    runs-on: latchkey-small
    env:
      GOFLAGS: -mod=vendor
      GOARCH: amd64
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Check Vendor Directory
        # ensure our vendor directory matches up with our go modules
        run: |
          go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1)
      - name: Check go.mod file
        # ensure our go.mod file is clean
        run: |
          go mod tidy && git diff --exit-code || (echo "go.mod file is not clean. Run 'go mod tidy' locally and commit the changes" && exit 1)
      - name: Check All Auto-Generated Files
        # ensure all our auto-generated files are up to date
        run: |
          go generate ./... && git diff --quiet || (git status -s; echo "Auto-generated files not up to date. Run 'go generate ./...' locally and commit the changes" && exit 1)
        shell: bash # needed so that we get "-o pipefail"
      - name: Check Filenames
        run: scripts/check_filenames.sh
  lint:
    timeout-minutes: 30
    runs-on: latchkey-small
    env:
      GOFLAGS: -mod=vendor
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
      - name: Check formatting
        run: ./scripts/gofumpt-check.sh
      - name: Lint
        # Run even if the formatting check failed, so that both sets of
        # problems are reported in a single CI run.
        if: ${{ !cancelled() }}
        uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
        with:
          # If you change this, make sure to also update scripts/golangci-lint-shim.sh
          version: v2.4.0
  upload-coverage:
    timeout-minutes: 30
    # List all jobs that produce coverage files
    needs: [unit-tests, integration-tests]
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: latchkey-small
    steps:
      - name: Checkout code
        uses: actions/checkout@v7
 
      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version: 1.25.x
 
      - name: Download all coverage artifacts
        uses: actions/download-artifact@v8
        with:
          path: /tmp/code_coverage
 
      - name: Combine coverage files
        run: |
          # Find all directories in /tmp/code_coverage and create a comma-separated list
          COVERAGE_DIRS=$(find /tmp/code_coverage -mindepth 1 -maxdepth 1 -type d -printf '/tmp/code_coverage/%f,' | sed 's/,$//')
          echo "Coverage directories: $COVERAGE_DIRS"
          # Run the combine command with the generated list
          go tool covdata textfmt -i=$COVERAGE_DIRS -o coverage.out
          echo "Combined coverage:"
          go tool cover -func coverage.out | tail -1 | awk '{print $3}'
 
      - name: Upload to Codacy
        run: |
          CODACY_PROJECT_TOKEN="${CODACY_PROJECT_TOKEN}" \
            bash <(curl -Ls https://coverage.codacy.com/get.sh) report \
            --force-coverage-parser go -r coverage.out
 
        env:
          CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
  check-for-fixups:
    timeout-minutes: 30
    runs-on: latchkey-small
    if: github.ref != 'refs/heads/master'
    steps:
      # See https://github.com/actions/checkout/issues/552#issuecomment-1167086216
      - name: "PR commits"
        run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"
 
      - name: "Checkout PR branch and all PR commits"
        uses: actions/checkout@v7
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: ${{ env.PR_FETCH_DEPTH }}
 
      - name: Check for fixups
        run: |
          ./scripts/check_for_fixups.sh ${{ github.event.pull_request.base.ref }}
 

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 7 jobs (11 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