Skip to content
Latchkey

Build and Release workflow (HolmesGPT/holmesgpt)

The Build and Release workflow from HolmesGPT/holmesgpt, explained and optimized by Latchkey.

C

CI health: C - fair

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

Grade your own workflow free or run it on Latchkey →
Source: HolmesGPT/holmesgpt.github/workflows/build-binaries-and-brew.yamlLicense Apache-2.0View source

What it does

This is the Build and Release workflow from the HolmesGPT/holmesgpt repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Build and Release

on:
  release:
    types: [created]

jobs:
  build:
    strategy:
      matrix:
        include:
          # Linux AMD64 - build on older Ubuntu for forward compatibility
          # See https://pyinstaller.org/en/stable/usage.html?highlight=glibc#making-gnu-linux-apps-forward-compatible
          - os: ubuntu-22.04
            platform: linux
            arch: amd64
          # macOS ARM64 (Apple Silicon) - macos-14+ runs on M1/M2
          - os: macos-14
            platform: darwin
            arch: arm64
          # Windows AMD64
          - os: windows-latest
            platform: windows
            arch: amd64

    runs-on: ${{ matrix.os }}

    steps:
    - uses: actions/checkout@v2

    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.11'

    - name: Install dependencies (Unix)
      if: matrix.platform != 'windows'
      run: |
        python -m pip install --upgrade pip "setuptools<75" "pyinstaller>=6.11"
        curl -sSL https://install.python-poetry.org | python3 - --version 1.4.0
        poetry config virtualenvs.create false
        poetry install --no-root

    - name: Install dependencies (Windows)
      if: matrix.platform == 'windows'
      run: |
        python -m pip install --upgrade pip "setuptools<75" "pyinstaller>=6.11"
        curl -sSL https://install.python-poetry.org | python3 - --version 1.4.0
        C:\Users\runneradmin\AppData\Roaming\Python\Scripts\poetry config virtualenvs.create false
        C:\Users\runneradmin\AppData\Roaming\Python\Scripts\poetry install --no-root

    - name: Install dependencies (Linux)
      if: matrix.platform == 'linux'
      run: |
        sudo apt-get install -y binutils

    - name: Update package version (Linux)
      if: matrix.platform == 'linux'
      run: sed -i 's/__version__ = .*/__version__ = "${{ github.ref_name }}"/g' holmes/__init__.py

    # mac has BSD style sed command where you specify -i '' and not just -i
    - name: Update package version (macOS)
      if: matrix.platform == 'darwin'
      run: sed -i '' 's/__version__ = .*/__version__ = "${{ github.ref_name }}"/g' holmes/__init__.py

    # windows has no sed, so we use powershell
    - name: Update package version (Windows)
      if: matrix.platform == 'windows'
      run: |
        $filePath = 'holmes/__init__.py'
        (Get-Content $filePath) -replace '__version__ = .+', '__version__ = "${{ github.ref_name }}"' | Set-Content $filePath
      shell: pwsh

    - name: Install unixODBC (macOS)
      if: matrix.platform == 'darwin'
      run: brew install unixodbc

    # if you change something here, you must also change it in .github/workflows/build-and-test.yaml
    - name: Build with PyInstaller
      shell: bash
      # regarding the tiktoken part of the command, see https://github.com/openai/tiktoken/issues/80
      # regarding the litellm part of the command, see https://github.com/pyinstaller/pyinstaller/issues/8620#issuecomment-2186540504
      run: |
       pyinstaller holmes_cli.py \
            --name holmes \
            --add-data 'holmes/plugins/skills/*:holmes/plugins/skills' \
            --add-data 'holmes/plugins/prompts/*:holmes/plugins/prompts' \
            --add-data 'holmes/plugins/toolsets/*:holmes/plugins/toolsets' \
            --add-data 'holmes/plugins/toolsets/coralogix*:holmes/plugins/toolsets/coralogix' \
            --add-data 'holmes/plugins/toolsets/grafana*:holmes/plugins/toolsets/grafana' \
            --add-data 'holmes/plugins/toolsets/internet*:holmes/plugins/toolsets/internet' \
            --add-data 'holmes/plugins/toolsets/elasticsearch*:holmes/plugins/toolsets/elasticsearch' \
            --add-data 'holmes/plugins/toolsets/prometheus*:holmes/plugins/toolsets/prometheus' \
            --hidden-import=tiktoken_ext.openai_public \
            --hidden-import=tiktoken_ext \
            --hidden-import=backports \
            --hiddenimport litellm.llms.tokenizers \
            --hiddenimport litellm.litellm_core_utils.tokenizers \
            --collect-data litellm
        ls dist

    - name: Zip the application (Unix and Mac)
      if: matrix.platform != 'windows'
      run: |
        cd dist
        zip -r --symlinks holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip holmes
        mv holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip ../
        cd ..

    - name: Zip the application (Windows)
      if: matrix.platform == 'windows'
      run: |
        Set-Location -Path dist
        Compress-Archive -Path holmes -DestinationPath holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip -Force
        Move-Item -Path holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip -Destination ..\
        Set-Location -Path ..

    - name: Upload Release Asset
      uses: actions/upload-release-asset@v1.0.2
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ github.event.release.upload_url }}
        asset_path: ./holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip
        asset_name: holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip
        asset_content_type: application/octet-stream

    - name: Upload build as artifact
      uses: actions/upload-artifact@v4
      with:
        name: holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}
        path: ./holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip

  check-latest:
    needs: build
    runs-on: ubuntu-22.04
    outputs:
      IS_LATEST: ${{ steps.check-latest.outputs.release == github.ref_name }}
    steps:
      - id: check-latest
        uses: pozetroninc/github-action-get-latest-release@v0.7.0
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          repository: ${{ github.repository }}
          excludes: prerelease, draft

  # Define macOS ARM64 hash job (Apple Silicon)
  mac-arm64-hash:
    needs: check-latest
    runs-on: ubuntu-latest
    if: needs.check-latest.outputs.IS_LATEST
    outputs:
      MAC_ARM64_BUILD_HASH: ${{ steps.calc-hash.outputs.MAC_ARM64_BUILD_HASH }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      - name: Download macOS ARM64 artifact
        uses: actions/download-artifact@v4
        with:
          name: holmes-darwin-arm64-${{ github.ref_name }}
      - name: Calculate hash
        id: calc-hash
        run: echo "MAC_ARM64_BUILD_HASH=$(sha256sum holmes-darwin-arm64-${{ github.ref_name }}.zip | awk '{print $1}')" >> $GITHUB_OUTPUT

  # Define Linux hash job
  linux-hash:
    needs: check-latest
    runs-on: ubuntu-latest
    if: needs.check-latest.outputs.IS_LATEST
    outputs:
      LINUX_BUILD_HASH: ${{ steps.calc-hash.outputs.LINUX_BUILD_HASH }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      - name: Download Linux artifact
        uses: actions/download-artifact@v4
        with:
          name: holmes-linux-amd64-${{ github.ref_name }}
      - name: Calculate hash
        id: calc-hash
        run: echo "LINUX_BUILD_HASH=$(sha256sum holmes-linux-amd64-${{ github.ref_name }}.zip | awk '{print $1}')" >> $GITHUB_OUTPUT

  # Update Homebrew formula with new binary URLs and hashes
  update-formula:
    needs: [mac-arm64-hash, linux-hash]
    runs-on: ubuntu-latest
    if: ${{ !github.event.release.prerelease }}
    steps:
      - name: Checkout homebrew-holmesgpt repository
        uses: actions/checkout@v2
        with:
          repository: robusta-dev/homebrew-holmesgpt
          token: ${{ secrets.MULTIREPO_GITHUB_TOKEN }}
      - name: Update holmesgpt.rb formula
        env:
          MAC_ARM64_BUILD_HASH: ${{ needs.mac-arm64-hash.outputs.MAC_ARM64_BUILD_HASH }}
          LINUX_BUILD_HASH: ${{ needs.linux-hash.outputs.LINUX_BUILD_HASH }}
          TAG_NAME: ${{ github.ref_name }}
        run: |
          # Update URLs and hashes using Python for reliability
          python3 << 'PYTHON_SCRIPT'
          import os
          import re

          mac_arm64_hash = os.environ['MAC_ARM64_BUILD_HASH']
          linux_hash = os.environ['LINUX_BUILD_HASH']
          tag = os.environ['TAG_NAME']

          with open('./Formula/holmesgpt.rb', 'r') as f:
              content = f.read()

          # Update macOS URL (handles both old and new naming)
          content = re.sub(
              r'(if OS\.mac\?\s+)url "[^"]+"',
              rf'\1url "https://github.com/HolmesGPT/holmesgpt/releases/download/{tag}/holmes-darwin-arm64-{tag}.zip"',
              content,
              flags=re.DOTALL
          )

          # Update macOS sha256
          content = re.sub(
              r'(if OS\.mac\?\s+url "[^"]+"\s+)sha256 "[^"]+"',
              rf'\1sha256 "{mac_arm64_hash}"',
              content,
              flags=re.DOTALL
          )

          # Update Linux URL (handles both old and new naming)
          content = re.sub(
              r'(elsif OS\.linux\?\s+)url "[^"]+"',
              rf'\1url "https://github.com/HolmesGPT/holmesgpt/releases/download/{tag}/holmes-linux-amd64-{tag}.zip"',
              content,
              flags=re.DOTALL
          )

          # Update Linux sha256
          content = re.sub(
              r'(elsif OS\.linux\?\s+url "[^"]+"\s+)sha256 "[^"]+"',
              rf'\1sha256 "{linux_hash}"',
              content,
              flags=re.DOTALL
          )

          with open('./Formula/holmesgpt.rb', 'w') as f:
              f.write(content)

          print("Updated formula:")
          print(content)
          PYTHON_SCRIPT
      - name: Commit and push changes
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git commit -am "Update formula for release ${{ github.ref_name }}"
          git push

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: Build and Release
 
on:
  release:
    types: [created]
 
jobs:
  build:
    timeout-minutes: 30
    strategy:
      matrix:
        include:
          # Linux AMD64 - build on older Ubuntu for forward compatibility
          # See https://pyinstaller.org/en/stable/usage.html?highlight=glibc#making-gnu-linux-apps-forward-compatible
          - os: ubuntu-22.04
            platform: linux
            arch: amd64
          # macOS ARM64 (Apple Silicon) - macos-14+ runs on M1/M2
          - os: macos-14
            platform: darwin
            arch: arm64
          # Windows AMD64
          - os: windows-latest
            platform: windows
            arch: amd64
 
    runs-on: ${{ matrix.os }}
 
    steps:
    - uses: actions/checkout@v2
 
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        cache: 'pip'
        python-version: '3.11'
 
    - name: Install dependencies (Unix)
      if: matrix.platform != 'windows'
      run: |
        python -m pip install --upgrade pip "setuptools<75" "pyinstaller>=6.11"
        curl -sSL https://install.python-poetry.org | python3 - --version 1.4.0
        poetry config virtualenvs.create false
        poetry install --no-root
 
    - name: Install dependencies (Windows)
      if: matrix.platform == 'windows'
      run: |
        python -m pip install --upgrade pip "setuptools<75" "pyinstaller>=6.11"
        curl -sSL https://install.python-poetry.org | python3 - --version 1.4.0
        C:\Users\runneradmin\AppData\Roaming\Python\Scripts\poetry config virtualenvs.create false
        C:\Users\runneradmin\AppData\Roaming\Python\Scripts\poetry install --no-root
 
    - name: Install dependencies (Linux)
      if: matrix.platform == 'linux'
      run: |
        sudo apt-get install -y binutils
 
    - name: Update package version (Linux)
      if: matrix.platform == 'linux'
      run: sed -i 's/__version__ = .*/__version__ = "${{ github.ref_name }}"/g' holmes/__init__.py
 
    # mac has BSD style sed command where you specify -i '' and not just -i
    - name: Update package version (macOS)
      if: matrix.platform == 'darwin'
      run: sed -i '' 's/__version__ = .*/__version__ = "${{ github.ref_name }}"/g' holmes/__init__.py
 
    # windows has no sed, so we use powershell
    - name: Update package version (Windows)
      if: matrix.platform == 'windows'
      run: |
        $filePath = 'holmes/__init__.py'
        (Get-Content $filePath) -replace '__version__ = .+', '__version__ = "${{ github.ref_name }}"' | Set-Content $filePath
      shell: pwsh
 
    - name: Install unixODBC (macOS)
      if: matrix.platform == 'darwin'
      run: brew install unixodbc
 
    # if you change something here, you must also change it in .github/workflows/build-and-test.yaml
    - name: Build with PyInstaller
      shell: bash
      # regarding the tiktoken part of the command, see https://github.com/openai/tiktoken/issues/80
      # regarding the litellm part of the command, see https://github.com/pyinstaller/pyinstaller/issues/8620#issuecomment-2186540504
      run: |
       pyinstaller holmes_cli.py \
            --name holmes \
            --add-data 'holmes/plugins/skills/*:holmes/plugins/skills' \
            --add-data 'holmes/plugins/prompts/*:holmes/plugins/prompts' \
            --add-data 'holmes/plugins/toolsets/*:holmes/plugins/toolsets' \
            --add-data 'holmes/plugins/toolsets/coralogix*:holmes/plugins/toolsets/coralogix' \
            --add-data 'holmes/plugins/toolsets/grafana*:holmes/plugins/toolsets/grafana' \
            --add-data 'holmes/plugins/toolsets/internet*:holmes/plugins/toolsets/internet' \
            --add-data 'holmes/plugins/toolsets/elasticsearch*:holmes/plugins/toolsets/elasticsearch' \
            --add-data 'holmes/plugins/toolsets/prometheus*:holmes/plugins/toolsets/prometheus' \
            --hidden-import=tiktoken_ext.openai_public \
            --hidden-import=tiktoken_ext \
            --hidden-import=backports \
            --hiddenimport litellm.llms.tokenizers \
            --hiddenimport litellm.litellm_core_utils.tokenizers \
            --collect-data litellm
        ls dist
 
    - name: Zip the application (Unix and Mac)
      if: matrix.platform != 'windows'
      run: |
        cd dist
        zip -r --symlinks holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip holmes
        mv holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip ../
        cd ..
 
    - name: Zip the application (Windows)
      if: matrix.platform == 'windows'
      run: |
        Set-Location -Path dist
        Compress-Archive -Path holmes -DestinationPath holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip -Force
        Move-Item -Path holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip -Destination ..\
        Set-Location -Path ..
 
    - name: Upload Release Asset
      uses: actions/upload-release-asset@v1.0.2
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ github.event.release.upload_url }}
        asset_path: ./holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip
        asset_name: holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip
        asset_content_type: application/octet-stream
 
    - name: Upload build as artifact
      uses: actions/upload-artifact@v4
      with:
        name: holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}
        path: ./holmes-${{ matrix.platform }}-${{ matrix.arch }}-${{ github.ref_name }}.zip
 
  check-latest:
    timeout-minutes: 30
    needs: build
    runs-on: latchkey-small
    outputs:
      IS_LATEST: ${{ steps.check-latest.outputs.release == github.ref_name }}
    steps:
      - id: check-latest
        uses: pozetroninc/github-action-get-latest-release@v0.7.0
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          repository: ${{ github.repository }}
          excludes: prerelease, draft
 
  # Define macOS ARM64 hash job (Apple Silicon)
  mac-arm64-hash:
    timeout-minutes: 30
    needs: check-latest
    runs-on: latchkey-small
    if: needs.check-latest.outputs.IS_LATEST
    outputs:
      MAC_ARM64_BUILD_HASH: ${{ steps.calc-hash.outputs.MAC_ARM64_BUILD_HASH }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      - name: Download macOS ARM64 artifact
        uses: actions/download-artifact@v4
        with:
          name: holmes-darwin-arm64-${{ github.ref_name }}
      - name: Calculate hash
        id: calc-hash
        run: echo "MAC_ARM64_BUILD_HASH=$(sha256sum holmes-darwin-arm64-${{ github.ref_name }}.zip | awk '{print $1}')" >> $GITHUB_OUTPUT
 
  # Define Linux hash job
  linux-hash:
    timeout-minutes: 30
    needs: check-latest
    runs-on: latchkey-small
    if: needs.check-latest.outputs.IS_LATEST
    outputs:
      LINUX_BUILD_HASH: ${{ steps.calc-hash.outputs.LINUX_BUILD_HASH }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      - name: Download Linux artifact
        uses: actions/download-artifact@v4
        with:
          name: holmes-linux-amd64-${{ github.ref_name }}
      - name: Calculate hash
        id: calc-hash
        run: echo "LINUX_BUILD_HASH=$(sha256sum holmes-linux-amd64-${{ github.ref_name }}.zip | awk '{print $1}')" >> $GITHUB_OUTPUT
 
  # Update Homebrew formula with new binary URLs and hashes
  update-formula:
    timeout-minutes: 30
    needs: [mac-arm64-hash, linux-hash]
    runs-on: latchkey-small
    if: ${{ !github.event.release.prerelease }}
    steps:
      - name: Checkout homebrew-holmesgpt repository
        uses: actions/checkout@v2
        with:
          repository: robusta-dev/homebrew-holmesgpt
          token: ${{ secrets.MULTIREPO_GITHUB_TOKEN }}
      - name: Update holmesgpt.rb formula
        env:
          MAC_ARM64_BUILD_HASH: ${{ needs.mac-arm64-hash.outputs.MAC_ARM64_BUILD_HASH }}
          LINUX_BUILD_HASH: ${{ needs.linux-hash.outputs.LINUX_BUILD_HASH }}
          TAG_NAME: ${{ github.ref_name }}
        run: |
          # Update URLs and hashes using Python for reliability
          python3 << 'PYTHON_SCRIPT'
          import os
          import re
 
          mac_arm64_hash = os.environ['MAC_ARM64_BUILD_HASH']
          linux_hash = os.environ['LINUX_BUILD_HASH']
          tag = os.environ['TAG_NAME']
 
          with open('./Formula/holmesgpt.rb', 'r') as f:
              content = f.read()
 
          # Update macOS URL (handles both old and new naming)
          content = re.sub(
              r'(if OS\.mac\?\s+)url "[^"]+"',
              rf'\1url "https://github.com/HolmesGPT/holmesgpt/releases/download/{tag}/holmes-darwin-arm64-{tag}.zip"',
              content,
              flags=re.DOTALL
          )
 
          # Update macOS sha256
          content = re.sub(
              r'(if OS\.mac\?\s+url "[^"]+"\s+)sha256 "[^"]+"',
              rf'\1sha256 "{mac_arm64_hash}"',
              content,
              flags=re.DOTALL
          )
 
          # Update Linux URL (handles both old and new naming)
          content = re.sub(
              r'(elsif OS\.linux\?\s+)url "[^"]+"',
              rf'\1url "https://github.com/HolmesGPT/holmesgpt/releases/download/{tag}/holmes-linux-amd64-{tag}.zip"',
              content,
              flags=re.DOTALL
          )
 
          # Update Linux sha256
          content = re.sub(
              r'(elsif OS\.linux\?\s+url "[^"]+"\s+)sha256 "[^"]+"',
              rf'\1sha256 "{linux_hash}"',
              content,
              flags=re.DOTALL
          )
 
          with open('./Formula/holmesgpt.rb', 'w') as f:
              f.write(content)
 
          print("Updated formula:")
          print(content)
          PYTHON_SCRIPT
      - name: Commit and push changes
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git commit -am "Update formula for release ${{ github.ref_name }}"
          git push
 

What changed

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:

This workflow runs 5 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