CI overview#

CCCL runs its build and test matrix on NVIDIA’s self-hosted GitHub Actions runners. The pipeline turns one push into hundreds of jobs across CUDA Toolkit versions, host compilers, GPU architectures, C++ standards, and operating systems.

Triggering and the copy-pr-bot security model#

CCCL’s runners have access to NVIDIA infrastructure, so arbitrary fork code must never run on them unreviewed. The copy-pr-bot GitHub App enforces this. CI does not trigger on pull_request events. It triggers on pushes to pull-request/<N> branches in the main repository, where <N> is the PR number. copy-pr-bot owns those branches and only writes vetted commits to them.

For external contributors, CI does not begin until a maintainer leaves an /ok to test [commit SHA] comment. copy-pr-bot verifies the SHA, copies the approved code to the pull-request/<N> branch, and the push to that branch starts CI. Every new commit on an external PR needs a fresh /ok to test.

For NVIDIA enterprise members with signed commits, the bot establishes identity from the commit signature and copies pushes automatically. CI begins immediately on each push, with no comment required. See the copy-pr-bot documentation for signing setup. Draft PRs do not auto-trigger — auto_sync_draft: false in .github/copy-pr-bot.yaml holds the bot back until the PR is ready.

SSH signing keys#

Signed commits are required for any internal NVIDIA contributors who want the convenience of CI running automatically whenever a commit is pushed to a branch (i.e., doesn’t require using /ok to test).

This is not required for external contributions, which will always require an explicit /ok to test [commit SHA] comment from an approved account for each CI run.

To enable commit signing using your existing ssh key, set the following git options:

git config --global gpg.format ssh
git config --global user.signingKey ~/.ssh/YOUR_PUBLIC_KEY_FILE_HERE.pub

# These settings are optional. They tell git to automatically sign all new commits and tags.
# If these are set to false, use `git commit -S` to manually sign each commit.
git config --global commit.gpgsign true
git config --global tag.gpgsign true

Git is now configured to sign commits with your ssh key.

To complete the process, upload the public key to your GitHub Signing Keys in your browser or using the gh CLI tool:

gh ssh-key add ~/.ssh/YOUR_PUBLIC_KEY_FILE_HERE.pub --type signing

Make sure that the key is uploaded to ‘Signing Keys’, not just ‘Authentication Keys’. The same key may be used for both.

Building the job matrix#

ci/matrix.yaml is the authoritative matrix definition, described in detail at matrix.yaml reference. The build-workflow action (.github/actions/workflow-build/) reads it and produces the concrete job list for the run. The action selects a workflow key by trigger: pull_request for PRs, nightly and weekly for scheduled runs.

Before expansion, ci/inspect_changes.py compares the PR against its merge base and reports which projects changed, the mechanism covered at Change detection. The action filters the pull_request matrix to the changed projects and pulls reduced pull_request_lite entries for projects only affected through a dependency. A change anywhere in CCCL infrastructure marks every project dirty and runs the full matrix. The expansion mechanics — dependency propagation, tag defaults, list explosion, job_map — are in ci/matrix.yaml’s tags, jobs, and projects sections and the build-workflow.py action.

Expansion produces workflow.json: named groups (for example, “CUB GCC”), each carrying a standalone array and a two_stage array. A second step splits these into four buckets along two axes — operating system and structure:

linux_standalone     linux_two_stage
windows_standalone   windows_two_stage

ci-workflow-pull-request.yml consumes the four buckets as four parallel matrix dispatches, one per workflow-dispatch-<structure>-group-<os>.yml. The split exists because Linux and Windows need different runner images and shell tooling, and because standalone and two-stage jobs have different dependency shapes. The workflow file mechanics are covered in full at GitHub Actions workflows.

Standalone and two-stage jobs#

A standalone job builds and tests in one runner, sharing nothing with other jobs.

A two-stage job splits build from test into a generic single-producer multiple-consumer model. A single producer job creates artifacts (usually on a cheap non-GPU runner), which are then consumed in one or more test jobs, which may uses GPUs if needed.

Accelerating build times with sccache#

CCCL’s CI uses a heavily modified fork of sccache with improved CUDA support to cache compiler artifacts for files that haven’t changed and dramatically accelerate build times. Local builds inside CCCL’s Dev Containers can share the same cache such that local builds and CI jobs mutually benefit from accelerated build times. Follow the GitHub Authentication guide to enable this feature.

PR Branch Protections#

The pull request workflow produces a single job that gates PR mergability, named “CI.” This sentinel job depends on and checks for success of every required top-level job in the workflow.

This test also checks for skip tags, override matrix modifications, open benchmark requests, etc. and will block merging until the tree is restored to a mergeable state.

Override and skip enforcement#

A non-empty workflows.override in ci/matrix.yaml replaces the pull_request matrix entirely, detailed at Override matrix. Use it to run a subset of jobs when a full run would be unnecessary / wasteful during iteration. The override deliberately blocks merge: the full suite must run before any PR lands, so the override must be emptied and re-pushed before acceptance testing.

Skip tags in the last commit message on the branch, documented at Skip tags, drop job groups for the next run. They also block merge while present, forcing a clean final run:

git commit -m "README tidy-up [skip-matrix][skip-vdc][skip-docs][skip-tpt]"
git commit -m "Run PR benchmarks [bench-only]"

The recognized tags and their semantics are catalogued in Skip tags. [bench-only] is shorthand for the common benchmark-request combination.

Reproducing a failure locally#

CI jobs run the build and test scripts in ci/ inside the devcontainers described in .devcontainer/README.md. A failing job’s log names the exact container and script invocation. Pull the same container and run the same ci/build_<project>.sh or ci/test_<project>.sh line to reproduce the CI environment, as walked through at Reproducing CI locally. For targeted single-test iteration, ci/util/build_and_test_targets.sh builds and runs a named subset, covered at Build and run targeted tests; for a regression hunt, see Build and Bisect Utilities.