From reading robots to writing them
Continuous integration sounds like enterprise jargon; it is actually a simple, powerful idea. Every time someone opens a pull request, a robot checks out the code on a fresh machine and runs your checks - lint, tests, a build. If anything fails, the PR shows a red X and cannot merge. That is it. That one habit is why teams of hundreds ship all day without breaking main. Today you write your first workflow and watch it go from red to green on a real PR.
.github/workflows/ci.yml in your repo that runs on every pull request, the fluency to read the four keys of any workflow (on / jobs / steps / uses+run), the habit of keeping secrets out of your files, and - the actual skill - the ability to open a failed run and read the log to find out why.
YAML anatomy of a workflow 5 min live
A workflow is a .yml file that lives in .github/workflows/. GitHub finds it there automatically. It has four keys worth knowing on sight: on is the trigger event (what makes it run), jobs are named units of work, steps are the ordered actions inside a job, and each step is either uses: (a ready-made action from the marketplace) or run: (a shell command you write). It all runs on a fresh GitHub machine called a runner - not your laptop.
LiveA minimal workflow, key by key3 min▶
Here is the smallest workflow that does something visible. YAML is whitespace-sensitive - the indentation is the structure, so it must be spaces, never tabs.
on:- the event that triggers the run. Here, everypush. Common values:push,pull_request, and a manual button (see the self-study card).jobs:- one or more named units.greetis our only job;runs-on: ubuntu-latestasks GitHub for a fresh Ubuntu machine.steps:- the ordered list. Step oneusesthe marketplacecheckoutaction to pull your code onto the runner; step tworuns a shell command.
Self-studyRunners, and why it is not your laptop2 min read▶
A runner is a clean virtual machine GitHub spins up just for your run and throws away after. ubuntu-latest is the common default; windows-latest and macos-latest exist too. Because it is fresh every time, a workflow that passes there proves your project actually builds from scratch - not just on your machine with your half-remembered global installs. That is the whole value: the runner is an impartial judge with no memory of your setup.
Write real CI: lint + test on every PR 5 min live
The highest-value workflow for any team runs checks automatically on every pull request, so broken code cannot merge. The pattern is always the same five steps: check out the code, set up the language, install dependencies, run the linter, run the tests. The green check and red X you learned to read in the non-tech track are now yours to create.
LiveReading the CI recipe, step by step3 min▶
on: pull_request- the point of CI. Checks run before the merge, on the proposed change, so problems surface on the PR and never reach main.- checkout then setup -
actions/checkoutputs your code on the runner;actions/setup-node(or setup-python) installs the language. Swap those two for any stack. - install in a clean box -
npm ciinstalls exactly what the lockfile says on a fresh machine. This is where "works on my machine" goes to die. - lint AND test - two different safety nets: lint catches style and obvious mistakes, tests catch broken behavior. Run both.
- matrix builds (one-line mention) - add a
strategy: matrix:block to run the same job across several language versions at once. Powerful; save it for later.
CI is the reason a team of hundreds can move fast without constantly breaking main. No human remembers to run every test on every change under deadline pressure - but the robot never forgets, never gets tired, and never waves something through because it is Friday. It is the tireless reviewer that runs the full suite on every single PR, so the humans can spend their review time on judgment instead of mechanics.
Self-studyThe same recipe in Python2 min read▶
Nothing about CI is language-specific - only the setup and command lines change. For a Python project the shape is identical:
Checkout, set up the language, install deps, lint, test. Learn the shape once and you can write CI for any project you touch.
Secrets, artifacts, and reading a failed run 4 min live
Three things separate a toy workflow from a real one: getting secrets in safely, getting outputs back out, and - most important - being able to read a failure. The actual skill of Actions is not memorizing YAML; it is opening a red run and finding the line that broke.
LiveSecrets in, artifacts out, failures read3 min▶
- Secrets. Store any token in Settings → Secrets and variables → Actions, then reference it as
${{ secrets.NAME }}. Never paste a real key into the YAML - a hardcoded secret in a workflow file is a leaked secret the moment you push. - Artifacts. Save build output or a test report with
actions/upload-artifact, and download it later from the run page. Useful for coverage reports, built binaries, screenshots from a failed test. - Reading a failure. Go to the Actions tab → click the red run → expand the failed step → read the log bottom-up. The last error before the exit is almost always the real one; scroll up only if it points elsewhere.
- Re-run. A flaky network step? Use Re-run failed jobs on the run page rather than pushing an empty commit.
Self-studyOther triggers: manual, scheduled, reusable2 min read▶
workflow_dispatch- adds a "Run workflow" button in the Actions tab so you can trigger a run by hand. Perfect for a deploy or a one-off maintenance job you do not want on every push.- Scheduled (
on: schedulewith cron) - run on a timer, e.g. a nightly build or a weekly dependency check. The cron syntax is the standard five fields. - Reusable workflows - factor a common workflow into one file and call it from many repos with
uses:, so a whole org shares one CI definition instead of copy-pasting YAML everywhere.
Author your first CI ★ 10 min · everyone types
Write a workflow, open a PR, watch the check run, break it, fix it. This red-to-green loop on a real PR is the whole session in ten minutes.
In my-project, create the folder and file: mkdir -p .github/workflows, then create .github/workflows/ci.yml in your editor (or use GitHub's web workflow editor under the Actions tab).
Paste a minimal workflow: on: both pull_request and push, one job that runs actions/checkout@v4 then a trivial run: echo "CI passing" (or a real test command if your repo has one).
Commit it on a branch: git switch -c add-ci, git add .github, git commit -m "Add CI workflow", git push -u origin add-ci. Open a PR with gh pr create --fill.
Watch the check appear on the PR and turn green. Then make it fail on purpose - change the command to run: exit 1, push, and watch the check go red.
Fix it - restore the good command, push again, watch it go back to green. You just lived the entire CI loop.
Is your workflow production-grade? ★ 6 min · everyone scores
Tick what your ci.yml actually does, honestly. The unticked rows are your next commits - the score tells you how much your workflow really protects main.
The break-and-read drill ★ 5 min · everyone reads a log
The real skill is reading logs, not memorizing YAML. So we break it on purpose and practice finding the error - the move you will make hundreds of times.
Introduce a failing step on purpose - add a run: step with a command that does not exist, like run: npm run nonsense. Commit and push to your PR branch.
Go to the Actions tab and click the new red run. The failed job has a red X next to it.
Expand the failed step and read the log bottom-up. Find the actual error line - usually the last thing before the step exited non-zero.
Fix the step, push, and confirm the run goes green. Notice you diagnosed it entirely from the log - that is the transferable skill.
This week ◐ 30 min total
- Add real lint + test CI to one of your actual repos - not my-project, something you care about. Make it run on
pull_request. - Add one secret in Settings and reference it as
${{ secrets.NAME }}in a step, even just toechothat it exists (never echo its value). - Make a run fail and read the log - deliberately break a step, open the red run, find the error line, fix it. Reps build the reflex.
- Read the triggers self-study card and try adding
workflow_dispatchso you get a manual "Run workflow" button. - Optional deep end: the official Hello GitHub Actions and Test with Actions Skills exercises.
Three questions before you go 🎯 ◐ 90 seconds
1 · What does the on: key control in a workflow?
on: is the trigger. It names the event - a push, a pull request, a timer, a manual button - that causes GitHub to start the run.
2 · Why run CI on pull_request specifically?
The whole point of CI is catching problems before they reach main. Running on the pull request checks the change while it is still a proposal, so a red X blocks the merge.
3 · How do you use a token safely in a workflow?
Secrets live in Settings and are injected at run time as ${{ secrets.NAME }}. A token written into the workflow file is a leaked token the moment you push - it is in the git history forever.
Official GitHub Skills coverage
This session maps to GitHub's own free Skills exercises on Actions. Here is what today covers in full, and what the official exercises add.
Session 5 cheat sheet · pin this
on = trigger · jobs = units · steps = ordered actions · each step is uses: or run:..github/workflows/*.yml - GitHub finds it automatically. Spaces, never tabs.on: pull_request.runs-on: ubuntu-latest - a fresh machine every run, not your laptop.${{ secrets.NAME }}. Never hardcode a token.