CI runs your checks on every push — but only the checks you wrote
You ask Claude to "add CI" and it drops a file into
.github/workflows/. From then on, every push gets a green check or
a red X next to it on GitHub. Feels like safety. Now the uncomfortable
part: for an ML project, the default setup checks almost nothing you
actually care about.
What a workflow file actually is
A workflow is a YAML file with three parts:
name: model-checks
on: [pull_request] # WHEN to run
jobs:
check:
runs-on: ubuntu-latest # a fresh rented Linux box
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- run: pytest -q
No magic. on says when it triggers (push, pull_request, a cron
schedule). Each run: line is a shell command executed on a throwaway
machine. A step passes when its command exits with code 0. That exit
code is the entire API of CI. Green check = every step exited 0.
That's all a green check has ever meant.
Why "tests pass" is not "model is fine"
pytest exercises your code: does preprocess() handle empty
input, does the API return 200. Your model's quality lives somewhere
pytest never looks — in the data and the weights. Accuracy can crater
from 91% to 74% while every unit test stays green, because no test
asserts anything about predictions. A schema change upstream, a
"harmless" preprocessing tweak, a retrained checkpoint — all invisible
to a code-only workflow.
The fix is to add steps that fail on purpose:
- A data validation step — a script that checks row counts,
null rates, and column schema, and calls
sys.exit(1)when the data looks wrong. - An eval gate — a script that runs the model on a fixed eval set, computes the score, and exits nonzero if it's below a threshold you chose. Below 0.80? The PR physically cannot merge.
Each is ten lines of Python plus one run: line in the YAML.
Reading a red X
Click the X. GitHub shows the job's steps in order: green ticks down to the first red one, then grey "skipped" for everything after — a job stops at the first failing step. Open the red step's log and read from the bottom up; the exit reason is almost always in the last 20 lines. Don't rerun it hoping. The X is the system doing its job.
Run the editor. We parse a model-checks workflow into a dict — the shape we'll evaluate gates against for the rest of this lesson.