GitHub Actions & CI Basics
Every pull request at the studio runs automated checks before it can merge. When they pass you barely notice them; when they fail, a red cross blocks your work. This guide explains what those checks are, how to find out why one failed, and how to fix it without guesswork.
Step 1: Understand What CI Does
Continuous integration (CI) means every change is built and tested automatically, on a clean machine, as soon as it's pushed. It catches the problems that "works on my machine" hides: a file you forgot to commit, a test you didn't run, a dependency only you have installed.
GitHub Actions is the CI service built into GitHub. Each check you see on a pull request is a job from an Actions workflow. Branch protection requires the important ones to pass before merging (see Repository Standards).
Step 2: Read a Workflow File
Workflows are YAML files in .github/workflows/. Indentation matters in YAML: use spaces, never tabs. A typical pull request workflow for a .NET service:
# .github/workflows/ci.yml
name: CI
on: # triggers: when the workflow runs
pull_request:
branches: [Dev, QA, main]
workflow_dispatch: # also allow manual runs
permissions:
contents: read # least privilege for the job's token
jobs:
build-and-test: # the check name shown on the PR
runs-on: ubuntu-latest # the runner: a fresh virtual machine
steps:
- uses: actions/checkout@v4 # a reusable action: get the code
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- run: dotnet restore # a shell command
- run: dotnet build --no-restore --configuration Release
- run: dotnet test --no-build --configuration Release
| Term | Meaning |
|---|---|
| Workflow | One YAML file: a set of jobs and the events that trigger them. |
Trigger (on) | The event that starts it: a pull request, a push, a schedule, a manual run. |
| Job | A group of steps that run on one machine. Jobs run in parallel unless one needs another. |
| Step | Either a shell command (run) or a reusable action (uses). |
| Runner | The machine that runs the job, fresh for every run. |
| Run | One execution of a workflow, with its own logs. |
Step 3: Find Out Why a Check Failed
On the pull request, scroll to the checks box and click Details next to the failed check. Expand the step with the red cross; the actual error is usually in the last 20 to 50 lines, just above "Process completed with exit code 1". The terminal is often faster:
gh pr checks # status of every check on your branch's PR
gh run list --branch feature/issue-42 # recent runs for a branch
gh run view <run-id> --log-failed # only the logs of the steps that failed
gh run watch # follow a run live until it finishes
Step 4: Diagnose the Failure
Nearly every failure falls into one of these categories. Identify which one before changing anything.
| What the log shows | Likely cause | What to do |
|---|---|---|
| Compiler errors | Code doesn't build on a clean machine: a missing file, or a merge with Dev broke it. | Rebase on Dev and build locally. Check git status for files you forgot to add. |
| Test failures | Your change broke behavior, or a test needs updating. | Run the same test locally. Fix the code or, if the behavior change is intended, the test. |
| Lint or format errors | Code style rules. | Run the project's formatter or linter locally (see the README) and commit the result. |
| Passes on re-run, fails at random | A flaky test: timing, ordering, or shared state. | Open an issue for the flaky test. Don't just keep re-running it. |
| Timeouts, network errors, "runner lost" | Infrastructure problems, not your code. | Re-run once. If it persists, tell your lead. |
| Empty secret, "unauthorized" | A missing or expired repository secret. | Tell your lead. Don't try to work around it. |
The most reliable fix is to reproduce the failure locally by running the same commands the workflow runs, copied from its run: lines. If it passes locally but fails in CI, look for differences: an uncommitted file, a different tool version, a file path that's case-sensitive on Linux but not on Windows or macOS, or a test that depends on your time zone.
Step 5: Re-run and Trigger Workflows
gh run rerun <run-id> --failed # re-run only the failed jobs
gh workflow list # workflows in this repository
gh workflow run ci.yml --ref feature/issue-42 # start a manual run (needs workflow_dispatch)
Pushing a new commit to the branch re-runs the pull request checks automatically. Re-running is for infrastructure blips; repeated re-runs to get a flaky test to pass hide real problems.
Step 6: Handle Secrets and Permissions Safely
- Read secrets with
${{ secrets.NAME }}, set by admins withgh secret set. Never write a secret value into a workflow file. - Never print secrets. GitHub masks known secret values in logs, but transformed values (encoded, split, or partly printed) can slip through.
- Grant least privilege. Set
permissions:at the top of each workflow, and give write access only to jobs that need it. - Trust actions carefully. Every
uses:runs someone else's code with access to your repository. Prefer official actions (actions/…) and pin third-party ones to a release tag or, better, a full commit hash.
Step 7: Change a Workflow Safely
- Edit the workflow on a feature branch like any other change. Pull request workflows run with your branch's version of the file, so your pull request tests the change itself.
- Add
workflow_dispatchwhile you're iterating so you can trigger runs manually. - Keep jobs fast: cache dependencies, and run slow suites (end-to-end tests, Unity builds) only where they're needed.
- Changes to
.github/workflows/need review from the owners listed inCODEOWNERS.