Workflow foundations
Run reproducible steps
Make workflow commands match documented local commands and pin the runtime they require.
CI should run the same commands a developer runs locally. If the workflow hides build logic in long inline shell blocks, you get a second private build that nobody tests on their laptop.
That is how “works in CI, fails locally” bugs appear.
Document the exact commands in README or AGENTS.md. Agents and new contributors should not guess a different install path than the workflow uses.
Windows and macOS developers on Linux CI need the same lockfile discipline. Pin the runner OS in docs when commands differ.
Put commands in package scripts
The workflow should orchestrate. Your repo scripts should do the work.
{
"scripts": {
"test": "vitest run",
"build": "astro build",
"check": "npm run test && npm run build"
}
}
Then the workflow stays short:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- run: npm ci
- run: npm test
- run: npm run build
A new contributor can run npm test before pushing. The CI log shows the same command names they already know.
Pin the runtime
The runner image is not your laptop. Declare the Node, Python, or Go version explicitly.
- uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: npm
If your repo has a .node-version file, reference it. One file, one source of truth.
Reproduce locally before you trust green
Start from a clean checkout. Run every workflow command in order:
npm ci
npm test
npm run build
Save the terminal output. When CI fails, compare line by line.
A realistic failure: CI passes because npm ci used a warm cache with stale packages. Locally you run npm install and get different results. Fix the lockfile, not the workflow wording.
Long inline shell in YAML is also hard to review in pull requests. Moving logic into scripts/ci-build.sh gives you normal diff review and local execution.
Try this on your own project: copy the exact run: steps into your README or AGENTS.md so agents and humans run the same path.
Lesson completed