How Our Main CI Workflow Actually Works

2026/04/08

BUILDcigithub-actionsdevex

Every PR merge triggers a full CI run against main. The whole thing is one YAML file, about 200 lines, and it does exactly three things: dispatch work to reusable workflows, aggregate the results, and tell us when something breaks. Here’s how.

The trigger

on:
  push:
    branches: [main]
  workflow_dispatch:

That’s it. Every push to main fires the workflow. Since we use a merge queue, “every push to main” effectively means “every merged PR.” There’s also workflow_dispatch so we can re-run it manually when debugging. No webhook server, no cron, no external orchestrator. Just GitHub’s built-in event system.

The dispatcher pattern

The workflow file itself doesn’t run a single step. It’s a pure dispatcher. Seven jobs: entries, each one a uses: call to a reusable workflow:

Job Reusable Workflow What it does
lint lint_checks.yml Linting
cpu-tests run_cpu_tests.yml CPU test suite with auto retry
gpu-tests run_gpu_tests.yml GPU tests (default cluster)
gpu-tests-falcon run_gpu_tests.yml GPU tests (Falcon PHX cluster, different marker)
gpu-tests-rocket run_gpu_tests_rocket.yml Rocket GPU tests
logcollector-tests run_logcollector_tests.yml Log collector tests
container-builds build_yolo_base_and_rocket_containers.yml Docker image builds

Each job passes down its secrets and config via with: and secrets:. The dispatcher knows what to run and with what inputs. The reusable workflows know how to run it. Clean separation.

Notice run_gpu_tests.yml appears twice with different parameters. Same workflow, different cluster config and test markers. That’s the whole point of reusable workflows.

Concurrency

Two layers of concurrency control, and they’re intentionally different:

Top-level (the dispatcher itself): cancel-in-progress: false. If two PRs merge back-to-back, both CI runs proceed. You don’t want a newer merge to cancel the older one’s CI, because you need to know if that specific commit is green.

Per-job (inside each reusable workflow): cancel-in-progress: true. If the same job is running for an older commit and a newer one comes in, the older one gets cancelled. No point finishing a GPU test suite for commit A when commit B already superseded it.

The result: the dispatcher always runs, but individual jobs are efficient about not doing redundant work.

Aggregation jobs

This is where it gets interesting. Two jobs sit at the bottom of the file, after all seven dispatched jobs.

all-done

Depends on every job. Runs with if: always(), so it executes even if something upstream failed or was cancelled. Its only purpose: if everything passed, force-update a git tag called last-green-ci to point at the current commit.

- name: Update last-green-ci tag
  run: |
    gh api repos/$REPO/git/refs/tags/last-green-ci \
      --method PATCH \
      --field sha=${{ github.sha }}

There’s retry logic (3 attempts) because the GitHub API occasionally 409s when two runs try to move the tag simultaneously. The tag is our “last known good” pointer. Downstream tools, like the test investigation pipeline, use it to know which commit was the last fully-green main build.

send_slack_notification

Fires only on failure or cancellation. Posts a message to our CI alerts Slack channel with the commit, author, and which jobs failed. It also saves the Slack message timestamp as a workflow artifact so that follow-up workflows (like the AI failure analysis pipeline) can thread their replies onto the same message instead of spamming the channel.

The honest disclaimer

There’s a comment at the top of the file that says, roughly: “this is a wildly bad idea.”

The dispatcher is a near-copy of our PR CI configuration. Same reusable workflows, same parameters, slightly different trigger. The proper fix would be a single workflow that accepts workflow_call, so both PR CI and main CI invoke the same source of truth. But duplicating the file was the fastest way to get required CI running on main, and “fast and wrong” beat “correct and not shipped” that week.

It’s still duplicated. We know. It’s on the list.

Why this pattern works

The dispatcher + aggregation pattern gives you three properties that are surprisingly hard to get in GitHub Actions:

  1. One file to understand the whole pipeline. Open the dispatcher, see all seven jobs, done. You don’t need to trace triggers across multiple workflow files.
  2. A reliable “last known good” pointer. The last-green-ci tag is a single atomic artifact that downstream systems can trust. No querying the API for “most recent successful run.”
  3. Failure notifications with threading. The Slack message timestamp artifact lets multiple systems contribute to the same conversation thread instead of flooding the channel.

Is it the cleanest possible design? No. The file says so itself. But it’s simple enough that any engineer can open it and understand what main CI does in about 30 seconds. That’s worth something.