A red GitHub Actions job often invites a speculative edit: change a path, relax an exit code, or rerun until it passes. A faster boundary is to prove what the runner can actually see before changing behavior.
1. npm ci says the lockfile is missing or stale
Start in the directory where the install command runs. Print the path, list the manifest and lockfile, and compare the dependency declaration with the committed lockfile. In a monorepo, checkout places the repository on disk; it does not choose the package for later run steps.
Use a run-step working directory or an explicit command such as:
- name: Install API dependencies
working-directory: services/api
run: npm ci
Do not replace npm ci with npm install merely to make CI green. That changes the reproducibility boundary instead of proving why the frozen install failed.
2. The lockfile exists, but setup-node cannot find it
Action inputs are normally repository-relative even when later shell commands run from a package directory. If the lockfile is at services/api/package-lock.json, make that path explicit:
- uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
cache-dependency-path: services/api/package-lock.json
Then keep the install step's working-directory aligned with the same package. These are two separate path decisions.
3. upload-artifact reports that no files were found
Prove that at least one visible output exists before the upload step. On an Ubuntu runner with Bash:
- name: Prove artifact output
shell: bash
run: |
test -d dist
find dist -type f ! -path '*/.*' -print -quit | grep -q .
Checking only that dist exists is insufficient: an empty directory still satisfies test -d. Hidden-only output can also diverge from the files the upload action includes. Keep the proof under the same runner and shell assumptions as the build.
4. pytest exits 5 with no tests collected
Exit code 5 means pytest collected no tests; it does not mean an assertion failed. From the same directory and configuration as CI, run:
python -m pytest --collect-only -q tests
If collection is empty, inspect the run step's working directory, testpaths, naming rules, and any -k, marker, node-id, or path filter. Appending || true can hide assertion failures and collection errors as well as an intentionally empty suite.
A free first pass
CI Rescue is a free, MIT-licensed GitHub Action that reads one workflow and an optional saved, sanitized log offline. It writes a Markdown report and job summary; it does not edit the inspected files or make network requests. A clean report means no bundled rule matched, not that a hosted run is guaranteed to pass.
Install and source:
github.com/Yunczo/ci-rescue-service/releases/tag/v1.0.1
The four field guides with commands, boundaries, and primary references are here:
yunczo.github.io/ci-rescue-service/#guides
If the free report still leaves one hosted job unresolved, CI Rescue also offers a separate fixed-scope repair for one workflow at $49 USD equivalent in Bitcoin. Send sanitized inputs for written scope review first; no login or email is required, and no payment is requested until the scope is accepted in writing:
yunczo.github.io/ci-rescue-service/#anonymous-intake
No prior paid-client result is claimed. Security and vulnerability work is excluded.
