learn-github-for-builders-with-phoebe / Session 5 of 6
Learn GitHub for Builders with Phoebe · Session 5 of 6 · Tech track

Actions authoring: writing the robots

In the non-tech track you learned to read the robots - the green checks and red Xs that appear on a PR. Today you write them. A GitHub Actions workflow is a small YAML file that runs your checks automatically on GitHub's machines, so broken code never quietly reaches main. We build the single highest-value workflow any team owns: lint and test on every pull request.

🟠 Stretch Builders & analysts Terminal or Codespaces 45 min live + self-study
0-3 · Welcome 3-18 · Concepts 18-40 · Build-along 40-45 · Q&A
Part 0

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.

Live - presented in session Self-study - read after class ★ Build-along - type it in your terminal Running artifact: your my-project repo
★ What you walk out with today A real .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.
Part 1 · the shape of a workflow

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.

ci.yml - nested keys, outer to inner on: pull_request the trigger event jobs: ci a named unit, runs on a runner steps: uses: actions/checkout run: npm test uses = a marketplace action run = a shell command event runner steps green check ✓
🔍 Click to zoom - on / jobs / steps nest outer to inner; each step is a uses or a run; the flow goes event → runner → steps → check
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.

# .github/workflows/hello.yml name: Hello on: push jobs: greet: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: echo "Hello from a GitHub runner"
  • on: - the event that triggers the run. Here, every push. Common values: push, pull_request, and a manual button (see the self-study card).
  • jobs: - one or more named units. greet is our only job; runs-on: ubuntu-latest asks GitHub for a fresh Ubuntu machine.
  • steps: - the ordered list. Step one uses the marketplace checkout action to pull your code onto the runner; step two runs 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.

Part 2 · the workflow worth writing

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.

# .github/workflows/ci.yml - runs on every PR and push to main name: CI on: pull_request: push: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npm run lint - run: npm test
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/checkout puts 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 ci installs 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.
Real world

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:

# the middle of a Python ci.yml job - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install -r requirements.txt - run: ruff check . - run: pytest

Checkout, set up the language, install deps, lint, test. Learn the shape once and you can write CI for any project you touch.

Part 3 · secrets, artifacts, and reading failures

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.
# referencing a secret safely inside a step - run: ./deploy.sh env: API_TOKEN: ${{ secrets.API_TOKEN }} # uploading a report as an artifact - uses: actions/upload-artifact@v4 with: name: coverage path: coverage/
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: schedule with 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.
Build-along 1 of 3

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.

One PR, six real commands BRANCH git switch -c add-ci STAGE git add .github COMMIT git commit -m "Add CI workflow" PUSH git push -u add-ci OPEN PR gh pr create --fill CHECK red, then green The red-to-green loop on a real PR: break the check on purpose, then fix it and watch it pass again.
🔍 Click to zoom - five commands take a branch from idea to a green check

Fix it - restore the good command, push again, watch it go back to green. You just lived the entire CI loop.

Build-along 2 of 3

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.

Build-along 3 of 3

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.

Reading a failed run: top-down vs bottom-up ✗ Read top to bottom pages of passing output first real error is near the bottom easy to miss, easy to misread ✓ Read log bottom-up "npm run nonsense" last line before exit = cause scroll up only if it points The last error before the exit is almost always the real one - scroll up only if it points elsewhere.
🔍 Click to zoom - the real error sits right before the exit, read bottom-up

Fix the step, push, and confirm the run goes green. Notice you diagnosed it entirely from the log - that is the transferable skill.

Read logs bottom-up A failed run often prints pages of successful output first. The error you want is near the bottom, right before the step reports failure. Start there and scroll up only if it references something earlier.
After the session

This week ◐ 30 min total

Check yourself

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.

Source material

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.

✓Hello GitHub Actions (skills/hello-github-actions)author a workflow, the four keys, runners - Part 1 + Build-along 1
✓Test with Actions (skills/test-with-actions)CI lint + test on a PR - Part 2 + Build-alongs 1 & 3
✓SecretsSettings secrets + ${{ secrets.NAME }} - Part 3 + checklist
◐Workflow artifacts (skills/workflow-artifacts)upload-artifact shown - Part 3; full exercise goes deeper
◐Reusable workflows (skills/reusable-workflows)self-study card - concept only
◐Matrix buildsone-line mention in Part 2 - test across versions

Session 5 cheat sheet · pin this

The four keyson = trigger · jobs = units · steps = ordered actions · each step is uses: or run:.
Where it lives.github/workflows/*.yml - GitHub finds it automatically. Spaces, never tabs.
The CI recipecheckout → setup language → install deps → lint → test, triggered on: pull_request.
Runnersruns-on: ubuntu-latest - a fresh machine every run, not your laptop.
SecretsSettings → Secrets. Reference ${{ secrets.NAME }}. Never hardcode a token.
Read a failureActions tab → red run → expand failed step → read the log bottom-up.