Actions and checks
Use a remote, pull request and automated check to propose a reviewable change.
THE IDEA
A workflow is a recipe that runs when something happens
A workflow is a file in the repository, so it is reviewed, versioned and branched like code. It names events that start it (a push, a pull request), and one or more jobs. Each job runs on a fresh machine, called a runner, and consists of steps that run in order. The results appear on the pull request as checks, and branch protection can require them before merging.
name: CI
on: [push, pull_request] # events
jobs:
test: # a job
runs-on: ubuntu-latest # a fresh machine
steps: # run top to bottom
- uses: actions/checkout@v4 # get the code
- run: npm ci
- run: npm testThe runner below is a simulation. It reads a restricted subset of the real file format (events, jobs, needs, uses, run, env) and pretends to run a handful of commands such as npm ci, npm test and sleep. Real workflows support much more (matrices, conditions, caching, secrets, artifacts).
.github/workflows/ci.yml (the repository also has package.json, src/ and tests/)
Parallel jobs, sequential steps
Jobs without dependencies start at the same time. needs adds an edge: the job waits for the jobs it names, and if one of them fails it is skipped. Steps inside a job always run in order. So the elapsed time is the length of the longest chain, not the sum of all jobs.
Elapsed: 9 minutes. Adding every job up would give 13. The elapsed time is the longest chain of dependencies.
Predict first. The tests need the output of the build. Can the test job safely start before the build finishes?
Checks gate the merge, and secrets are not for strangers
A green check is evidence that the commands you wrote passed, not proof the software is correct: if no test covers a behavior, no check will notice it breaking. Branch protection turns checks into a gate: require a pull request, require the check named test. The rule names a job, so it must match a job that the workflow really produces.
Pull requests from forks come from people you may not know. By design they do not receive your repository's secrets, otherwise any stranger could print them in a workflow. That is why a step that needs a deploy key works on your own branch and gets an empty value on a fork's pull request.
Chapter context
GitHub hosts repositories and adds collaboration around Git objects. A pull request is a review and discussion mechanism; the commits and branch history still follow Git rules.
TRY IT YOURSELF
The repository has package.json, src/ and tests/. The workflow below was written in a hurry and is broken in more than one way. Fix it, add a lint job, then protect main. Press Run to see the parser or the runner speak.
- ○Fix the file until the parser accepts it (read the error line numbers)
- ○Give every job a checkout step before its commands: a fresh runner starts with an empty folder
- ○Run the workflow until every job passes
- ○Add a lint job (npm run lint) that needs build and runs at the same time as test
- ○Protect main: require pull requests and require the test check
.github/workflows/ci.yml (the repository also has package.json, src/ and tests/)
Branch protection for main (simulated settings page)
A check name is the name of a job. A rule that names a job your workflow does not produce can never be satisfied, and nothing could ever merge.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Tests depend on build; can they safely start before build finishes?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For independent jobs with durations t1 and t2 running simultaneously, ideal elapsed time is max(t1,t2), excluding startup and queueing. A dependency adds a sequential edge and can make durations add instead.
Worked example
A build takes 3 minutes. Two test jobs taking 4 and 6 minutes start after the build and run together. The ideal critical path is 3 + max(4, 6) = 9 minutes, not 13. Real runners can add scheduling overhead.
Reference code
The following example uses the Python standard library and does not call a remote service.
build, tests = 3, [4, 6]
assert build + max(tests) == 9
assert build + sum(tests) == 13
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
A green check is evidence for the checks that ran, not proof of every behavior. Forked code must not automatically receive trusted secrets.