Skip to content

GitHub Actions Style Guide

We use GitHub Actions to run automated checks, deployment pipelines etc. Each team maintains their own workflows but some common standards should be observed to ensure maintainability and prevent hard-to-diagnose issues. These standards are outlined here.

Creating Workflows

Workflows live under .github/workflows/. To make it easy to understand what they are for they should follow certain naming patterns. If you are creating a workflow for your project (where a project is typically a top-level directory in the monorepo), name your workflow file project-<my-project>.yml, e.g. project-infra.yml and give it a suitable name, i.e.:

  name: Project - Infra

Prefixing with project/"Project" makes sure workflows appear in a sensible order in .github/workflows/ and the Actions tab on GitHub.

Ideally also add a description: that explains what your workflow does.

Triggering Workflows

Basic test workflows typically run on pull request and pushes to main. Unless you have specific requirements for when to run your workflow consider triggering it on:

  pull_request:
    types: [opened, reopened, synchronize]
    paths:
      - '.github/workflows/my-workflow.yml'
      - ...
      - '!**/*.md'
  push:
    branches: [main]
    paths:
      - '.github/workflows/my-workflow.yml'
      - ...
      - '!**/*.md'

I.e.:

  • Run it on opened, reopened and synchronized PRs as well as pushes to main.

  • Run it when the workflow file itself or relevant files in your project (...) change.

  • Do not run it if only documentation has changed (unless you're deploying documentation somewhere).

Many workflows also benefit from being able to be triggered manually, e.g. if they are expensive to run or used to trigger certain events like releases. This requires adding workflow_dispatch trigger.

Workflow Inputs

When triggering a workflow via workflow_dispatch or workflow_call, you are often going to need to specify certain input variables. Here are some related tips:

  • For workflow_dispatch, use choice for enum-like inputs to create dropdowns in the GitHub UI, e.g.:

    workflow_dispatch:
      inputs:
        environment:
          description: Environment to deploy to
          type: choice
          options:
            - devel
            - integration
            - prod
          required: true
    
  • Because GitHub does not do this automatically for workflow_call, add a validate-inputs job to sanity-check inputs. E.g. for a "choice" input (which would be of type string for workflow_call because choice is not supported):

    validate-inputs:
      name: Validate correctness of workflow inputs
      runs-on: ubuntu-latest
      steps:
        - name: Validate environment
          run: |
            case "${{ inputs.environment }}" in
              devel|integration|prod) ;;
              *) echo "::error::Invalid environment: ${{ inputs.environment }}"; exit 1 ;;
            esac
    

Make sure to use the ::error::... pattern so that GitHub displays this as an actual error and not just log output. All other jobs should then depend on this one if they use any of the inputs.

Workflow Runners

For now just run all your jobs on ubuntu-latest until we decide otherwise.

Workflow Jobs

Workflows run one or more jobs. Give these easy to understand names, usually these will be sufficient:

  • (optional) build: Use this if you want to separate building from testing.

  • test: Run tests, optionally split into unit-tests/component-tests/integration-tests.

  • docker: Build and push Docker images.

Composite Actions

We provide some custom GitHub actions that you can reuse inside the monorepo under ./github/actions/. Some tips for writing these:

  • If something in an action is variable in the sense that we may want to update it periodically, but should always be the same across callers (e.g. a package version), do not expose it as an input but rather use the following pattern:
    using: composite
    steps:
      - name: Set constants
        id: constants
        shell: bash
        run: |
          echo "my_package_version=1.2.0" >> "$GITHUB_OUTPUT"
    

Freestanding Actions

We also implement some GitHub actions as freestanding repos. The latter is currently restricted to lyceum-tech/set-up-lyceum which you must use to check out our repo (i.e. do NOT use actions/checkout directly). You will typically want to use it as follows, adjust if you need to tweak other available options:

- name: Set up Lyceum
  uses: lyceum-tech/set-up-lyceum@v1
  with:
    github_vars: ${{ toJSON(vars) }}
    github_secrets: ${{ toJSON(secrets) }}
    uv_python_packages: my-package-1,my-package2

Where my-package-1 and my-package-1 are Python packages from the monorepo you want to install. You can also set this to none if you do not need any or leave it out in which case all of them will be installed.

Third-Party Actions

Using arbitrary third-party actions is dangerous and basically asking for a supply-chain attack. We try to prevent this in two ways:

  • We only allow whitelisted actions. Note that this is currently not enforced because this feature is not available for private repositories so please be careful when adding external actions.

  • We require non-Lyceum actions to always be pinned to a specific commit instead of just pecifying a version number, but with a comment indicating the corresponding version, e.g.:

    astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6
    

Workflow Environments/Secrets

Environment variables and secrets for use inside workflows should be defined at the organization level (ask the administrator if you need to add or modify them). As a convention prefix environment variables with LYC_ if possible. If you use the lyceum-tech/setup-up-lyceum action as described above these will be automatically made available in your environment.

Known Bugs/Quirks and Workarounds

GitHub Action bugs that are not related to Copilot are almost never addressed by Microsoft and so there is a long and growing list. We detail a couple that we have encountered and what to do about them here:

  • github.workspace is unreliable: This variable does usually not point where you think it does in containerized workflows. To work around this, especially when using this inside a composite action, simply do the following once and then use $GITHUB_WORKFSPACE (which is already correct) or env.GITHUB_WORKSPACE in the rest of the action:
    - name: Set GITHUB_WORKSPACE
      shell: bash
      run: |
        echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" >> $GITHUB_ENV