Compile in CI

zipline compile turns your Python confs into the compiled/ output that the Hub reads. Instead of running it locally before every commit, you can let CI run it on each push and commit the output back to the branch. This is what makes editing confs in the Zipline UI work end to end: you edit and commit a .py file in the browser, and CI produces the matching compiled confs.

One zipline compile builds every environment in the repo (teams.py → compiled/, teams.canary.py → compiled_canary/), so there is nothing to choose per environment. See Multi-Environment Compile & Deploy.

GitHub Actions workflow

Add .github/workflows/zipline_compile.yaml to your conf repo. Adjust CONF_ROOT (the folder containing teams.py), the paths filters to match it, and the zipline-ai version to match your Hub.

name: Zipline compile
 
on:
  push:
    paths:
      - "confs/**"
      # Its own output; also keeps the commit below from re-triggering this workflow
      - "!confs/compiled/**"
      - "!confs/compiled_*/**"
  workflow_dispatch:
 
# A newer push to the same branch supersedes an in-flight compile
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  compile:
    name: Zipline compile
    runs-on: ubuntu-latest
    permissions:
      contents: write
    env:
      CONF_ROOT: confs
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Install Zipline CLI
        run: pip install "zipline-ai==<your Hub version>"
 
      # --force skips the interactive confirmation for changed confs. Unsafe in-place changes to
      # confs that are already online still fail the job.
      - name: Compile
        working-directory: ${{ env.CONF_ROOT }}
        run: zipline compile --force
 
      - name: Commit compiled confs
        if: github.ref != format('refs/heads/{0}', github.event.repository.default_branch)
        run: |
          # Quoted so git matches compiled/ and every compiled_<env>/, and nothing else
          git add -A -- "${CONF_ROOT}/compiled*"
          if git diff --cached --quiet; then
            echo "Compiled confs are up to date."
            exit 0
          fi
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git commit -m "Update compiled confs"
          if ! git push origin HEAD:"${GITHUB_REF_NAME}"; then
            # Someone pushed meanwhile; that push starts its own run, which compiles it
            git fetch origin "${GITHUB_REF_NAME}"
            if [ "$(git rev-parse FETCH_HEAD)" != "${GITHUB_SHA}" ]; then
              echo "::notice::${GITHUB_REF_NAME} moved during compile; the newer run will commit its output."
              exit 0
            fi
            exit 1
          fi
 
      # The default branch is usually protected, so verify instead of pushing to it
      - name: Verify compiled confs are up to date
        if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
        run: |
          git add -A -- "${CONF_ROOT}/compiled*"
          if ! git diff --cached --quiet; then
            echo "::error::Compiled confs are out of date. Run 'zipline compile' in ${CONF_ROOT} and commit the result."
            git diff --cached --stat
            exit 1
          fi

If your confs live at the repository root, use CONF_ROOT: . and filter on "**" with the same !compiled/** and !compiled_*/** exclusions.

How it behaves

  • Feature branches: each push that changes a conf compiles it, and any change to the compiled output is committed back to the branch as Update compiled confs. Review it in the pull request alongside the source change.
  • The default branch: the job only verifies that the committed output is current. Merge through pull requests so the compiled output is already on the branch.
  • Compile errors fail the job and nothing is committed. This includes changing a conf that is already running online without bumping its version: compile refuses to overwrite it, so the change has to go out as a new version.
  • The output is deterministic. The same confs always produce the same compiled files, and removed confs disappear from compiled/, so the commit contains only real changes.
  • No credentials are needed. Compile reads your confs and writes files; it doesn't contact the Hub or your warehouse. Uploading still happens through your deploy step (zipline hub schedule-all or the Hub UI).

Things to know

  • The compile commit doesn't start other workflows. GitHub doesn't run workflows for commits pushed with the default GITHUB_TOKEN, so the Update compiled confs commit has no checks of its own. If your branch protection requires checks on the latest commit, push with a GitHub App token instead (pass it to actions/checkout as token).
  • Pull requests from forks can't be pushed to by the workflow. Contributors from forks need to run zipline compile themselves.
  • Environment variables your teams.py reads (for example os.environ.get(...) for a version string) end up in the compiled output. Keep them unset in this job, or set them to the values you want committed.
  • Pin the CLI version to the one your Hub runs, and upgrade both together, so the compiled output matches what the Hub expects.
  • Seeing the result in the Zipline UI: the editor's Checks tab shows this job's status when the GitHub connection has the Checks: read and Commit statuses: read permissions.
Edit this page