ci-failure-triage

verified

7dcf703e-c6b5-4e53-861c-65764ffc0161

Use when CI is red — read the right job/step/first-failing-line, classify env vs test vs build failures, reproduce locally, and avoid the blind re-run trap.

Metadata

Skill ID
7dcf703e-c6b5-4e53-861c-65764ffc0161
Version
1
Owner
387274b7-2891-478b-81b8-e11d5adb9319
Tags
cidebugginggithub-actionsdevopstroubleshooting
Signature
verified
Integrity
OK
Content hash
57a02f7cb483a940a4a1d200450de320a846abf74cbd7f8e59ebefa2f504c0f3
Created
2026-08-15T05:24:21Z

Skill file

Raw skill file (markdown source)
# CI Failure Triage

**Use when** CI is red and you need to find and fix the cause fast. The trap: blind re-runs and reading only the tail of the log. Follow the read order.

## The Read Order

```text
1. WHICH job failed?      (lint? test? build? deploy?)
2. WHICH step failed?     (within the job)
3. What's the FIRST failing line? (not the last)
4. CLASSIFY: env / test / build / lint / flaky
```

### Finding the first failure (not the tail)

```text
In a log, the LAST error is often a cascade. The FIRST error is the cause.

GitHub Actions: scroll to the first red "✖" or "Error:" line.
Bash pipelines: the first non-zero exit is the culprit.
```

## Classification

| Type | Signature | Fix approach |
|------|-----------|--------------|
| **Env failure** | "command not found", missing secret, wrong Python version | Recreate env locally |
| **Test failure** | `assert` / traceback / `FAILED` | Reproduce locally, fix code |
| **Build failure** | compile error, missing dep, linker error | Fix build config |
| **Lint failure** | `ruff`/`eslint`/`mypy` violations | Run linter locally |
| **Flaky** | Passes on re-run, random failure | flaky-test-triage skill |

## Reproduce Locally (The "Works on My Machine" Checklist)

```bash
# 1. Match the CI Python/node version exactly
python --version          # vs CI's version
node --version

# 2. Match the dependency versions
pip freeze                # vs CI's lockfile
# Better: use the lockfile
pip install -r requirements.lock

# 3. Run the EXACT command from CI
# (find it in the workflow yml, e.g. .github/workflows/ci.yml)
pytest tests/ -x --cov=src/ --cov-fail-under=80

# 4. Run the linter with the SAME config
ruff check src/ --select ALL
mypy src/ --strict
```

## Common Causes

### Missing secret / env var

```text
CI error: "Missing required environment variable: STRIPE_API_KEY"

Fix: add the secret in repo settings, or use a test mode that doesn't need it.
```

```yaml
# .github/workflows/ci.yml
env:
  STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}   # must exist in settings
```

### Version pin drift

```text
CI passes locally because your local deps differ.
Fix: pin exact versions in requirements.txt / package-lock.json,
and run CI in a fresh container (act or docker).
```

```bash
# Run GitHub Actions locally
# https://github.com/nektos/act
act pull_request   # simulates the CI workflow locally
```

### Ordering / flakiness

```text
A test that passes alone but fails in the full suite = ordering issue.
Run the full suite locally in the SAME order as CI:
pytest tests/ -x   # full run, not just the failing test
```

### Lint config mismatch

```text
Local ruff passes but CI ruff fails.
Fix: ensure the SAME ruff version and config (pyproject.toml) are used.
```

```bash
ruff --version          # match CI's pinned version
ruff check src/         # same command as CI
```

## The Blind Re-Run Trap

```text
Re-running CI without investigating is the #1 anti-pattern.

- If it's a flaky test -> re-run MASKS a real bug
- If it's a real failure -> re-run wastes 10 minutes
- If it's an env issue -> re-run does nothing

ALWAYS read the log and classify BEFORE re-running.
```

## GitHub Actions Specifics

```bash
# Re-run a failed job
gh run rerun <run-id> --failed

# View the failing step's log
gh run view <run-id> --log-failed

# List recent failed runs
gh run list --status=failure
```

```yaml
# .github/workflows/ci.yml — add useful logging to future-proof
- name: Run tests
  run: pytest tests/ -x -v --tb=short
  # -v verbose + --tb=short gives a compact but complete traceback
```

## Guardrails

- **Never blind re-run as a fix.** Classify first, then decide if re-run is appropriate.
- **Read the FIRST error, not the last.** Cascades hide the root cause.
- **Match the CI environment exactly.** Version drift is the #1 "works on my machine" cause.
- **Don't comment out failing tests to "make CI green".** That's deleting the alarm, not fixing the fire.
- **Log the exact CI command** in the workflow so local repro is one copy-paste.

## Pitfalls

| Pitfall | Fix |
|---------|-----|
| Blind re-run as the "fix" | Read the log and classify BEFORE any re-run |
| Reading only the log tail | Scroll to the FIRST "Error:" or "✖" |
| Version mismatch (works locally) | Pin versions; use `act` to simulate CI |
| Commenting out failing tests | Fix the test or the code, never silence CI |
| Not checking secrets/env | Verify all `${{ secrets.* }}` exist in settings |

## Verify / Checklist

- [ ] Failing job, step, and first-failing-line identified
- [ ] Failure classified (env/test/build/lint/flaky)
- [ ] Reproduced locally with matching version + command
- [ ] Root cause identified (not just "re-run passed")
- [ ] Fix applied and CI green after push
- [ ] No tests commented out or disabled

Attached files

No attached files.