A check went red. This is how to find out why without reading the whole workflow file, and how to reproduce it locally so you are debugging your change rather than the Actions UI.
Rule zero: never fix a red gate by weakening it. Widening a tag, retrying a test, or relaxing a lint to get green converts a defect into a permanent lie. Bounded retries are reserved for idempotent external setup operations such as pulling a digest-pinned image; they do not change test outcomes. If a failure is genuinely not your change, say so in the pull request and fix the cause.
Every reproduction below runs from the repository root unless it says otherwise, and assumes the demo app is up where the failing job had it:
docker compose -f docker/compose.yml up -d website
| Failure class | Workflow · job | First diagnostic step | Artifacts | Reproduce locally |
|---|---|---|---|---|
| Hygiene lint | pr.yml · repo-hygiene |
Read the failing step’s name: it says which linter | None; the log is the artifact | See hygiene lint |
| Catalog drift | pr.yml · scenario-catalog |
The diff in the log names the rows that disagree | None | python3 tools/check-scenarios.py --write-matrix |
| Per-stack static | pr.yml · ts-smoke, py-smoke, java-smoke |
Type, lint, and format failures are deterministic and reproduce exactly | None | See per-stack static |
| Smoke | pr.yml · java-smoke, ts-smoke, py-smoke |
Open the uploaded trace or Surefire XML before rerunning | artifacts/<stack>/<run-id>/smoke/ |
See smoke |
| Regression matrix leg | java.yml, ts.yml, python.yml |
Note which leg failed; one browser failing is a different bug from all of them failing | artifacts/<stack>/<run-id>/<slice>/ |
See regression leg |
| Nightly grid | nightly.yml · java-nightly-grid |
Check whether the local-driver leg passed on the same browser | artifacts/java/<run-id>/<slice>/surefire-reports/ |
See nightly grid |
Path-filtered workflows only run when your change touches their stack, so a
regression workflow that did not run is not a failure. See
../ci-workflows.md for the trigger map.
Five linters share one job, so read the step name first. Each is runnable alone:
docker run --rm -v "$PWD:/repo:ro" -w /repo rhysd/actionlint:1.7.12 -color
git ls-files -z '*.md' | xargs -0 npx --yes markdownlint-cli2@0.23.0
Commit-message failures name the offending commit and rule. The rules, and the
exact scope list, are in ../../CONTRIBUTING.md;
commitlint.config.cjs is the authority.
A link-check failure prints the file, the line, and the unreachable target. If
the target is genuinely unverifiable rather than wrong — an upstream host that
rate-limits — it belongs in .lycheeignore with a reason, not in a retry.
“This pull request changes .github/workflows/ but not docs/ci-workflows.md“
is not a lint failure: the CI guide mirrors the workflows and your change made it
untrue. Update the guide. Use [ci-guide-exempt] in the pull request title only
when the workflow edit has no semantic effect.
The job regenerates the matrix and diffs it. It fails when the committed matrix,
or the README copy of it, disagrees with scenarios/catalog.yml:
python3 tools/check-scenarios.py --write-matrix
git diff --exit-code docs/scenario-matrix.md
The output tells you which of the two things happened:
docs/scenario-matrix.md means you changed the catalog and did
not regenerate. Commit the regenerated file.docs/scenario-matrix.md.catalog … ids missing tests or test ids missing catalog rows means
the catalog and the tests genuinely disagree. Fix whichever is wrong — do not
flip a coverage: flag to silence it.These are deterministic: if CI failed, your machine will too.
cd stacks/ts-playwright
npm run typecheck
npm run lint
npm run format:check
cd stacks/python-playwright
ruff check .
ruff format --check .
mypy src tests
mvn -B -f stacks/java-selenium-testng/pom.xml clean test-compile
The smoke jobs run one browser against the demo app. Read the artifact before rerunning: a Playwright trace usually shows the cause in one pass, and a rerun that passes without explanation has taught you nothing.
cd stacks/ts-playwright
THE_INTERNET_BASE_URL=http://localhost:7080 npm run test:chromium:smoke
cd stacks/python-playwright
THE_INTERNET_BASE_URL=http://localhost:7080 pytest -m smoke --browser chromium
mvn -B -f stacks/java-selenium-testng/pom.xml -P CLI_Parameters test \
-DsuiteXmlFile=src/test/resources/smoke.xml \
-Dbrowser=chrome \
-DheadlessBrowser=true \
-DuseSeleniumGrid=false \
-DwebAppAddress=http://localhost:7080 \
-DtestRunnerAddress=http://localhost:7080
If a smoke test fails intermittently rather than consistently, stop and use
../flakiness-guide.md. Do not retag it to get green.
fail-fast is disabled, so every leg reports independently. Which legs failed is
the diagnosis:
Reproduce one leg by naming its project. Substitute the failing leg’s browser:
cd stacks/ts-playwright
THE_INTERNET_BASE_URL=http://localhost:7080 npx playwright test --project=firefox
cd stacks/python-playwright
THE_INTERNET_BASE_URL=http://localhost:7080 pytest --browser firefox
mvn -B -f stacks/java-selenium-testng/pom.xml -P CLI_Parameters clean test \
-DsuiteXmlFile=src/test/resources/regression.xml \
-Dbrowser=firefox \
-DheadlessBrowser=true \
-DuseSeleniumGrid=false \
-DwebAppAddress=http://localhost:7080 \
-DtestRunnerAddress=http://localhost:7080
nightly.yml runs Java twice per browser: once with a local driver, once against
Selenium Grid. Compare them before anything else, because the pair localizes the
fault for free:
The Grid services are pinned to the same Selenium image digests the nightly job uses, so a local Grid reproduces it. Start the app plus Grid from the repository root:
docker compose -f docker/compose.yml -f docker/compose.grid.yml up -d
Then run against the Grid from the stack directory:
cd stacks/java-selenium-testng
mvn -P CLI_Parameters test \
-DsuiteXmlFile=src/test/resources/regression.xml \
-Dbrowser=remote-chrome \
-DheadlessBrowser=true \
-DuseSeleniumGrid=true \
-DtestRunnerAddress=http://website:5000 \
-DwebAppAddress=http://localhost:7080 \
-DseleniumGridAddress=http://localhost:4444/wd/hub
Stop everything from the repository root when finished:
docker compose -f docker/compose.yml -f docker/compose.grid.yml down
A nightly failure gates nothing on its own, but it is the only signal for browsers the pull-request gate never exercises. Do not let it stay red — a permanently red nightly is the same as no nightly.