Troubleshooting¶
Symptom, cause, fix. Exit codes first, because they tell you which half of the page you're in.
| Exit code | Meaning |
|---|---|
0 |
Patch coverage met the threshold, or there was nothing coverable to gate. |
1 |
Patch coverage fell below the threshold. This is the gate doing its job. |
2 |
Broken run, bad setup, or a usage error. Never a coverage verdict. |
Exit 2 is the one to read carefully. A failed, empty or unparseable test run is a tool
error, not 0% coverage, so it turns the build red instead of quietly failing the gate.
The gate passed but the number looks too good¶
The most important entry on this page, because nothing fails and nothing is logged as an error.
Patch coverage only counts changed lines the coverage report actually mentions. A file the report never names contributes nothing, so anything that drops files from the report inflates the result instead of failing it.
Check the denominator first. Covered / changed executable lines in the PR comment, or
total_lines in the outputs. If it's 0 or much smaller than the diff, one of these is
happening:
- A report was dropped. A multi-project .NET solution writes one report per test project,
and a Maven reactor writes one per module. Count them:
find . -name coverage.cobertura.xml | wc -l, orfind . -path '*/target/site/jacoco/jacoco.xml' | wc -l. - The paths don't match. Coverage paths are often absolute or rooted differently from
git diffpaths. Usestrip_prefixto peel a known root. - A JaCoCo report was read as Cobertura. Both use
.xml. Brimyr picks by root element, but an explicitcoverage_file: report.xml:coberturaoverrides that and produces an empty report. Drop the format suffix or use:jacoco. excludeis too broad. A glob like*Migrations*usesfnmatch, where*crosses/, so it matches deeper than you might expect.
History is shallow, so the merge-base is unavailable.¶
actions/checkout defaults to fetch-depth: 1, and patch coverage needs the merge base.
Set fetch-depth: 0:
- uses: actions/checkout@v6
with: { fetch-depth: 0 }
If you let Brimyr do its own checkout, leave fetch_depth at its default of 0. Locally,
run git fetch --unshallow.
No merge-base could be determined for the given base and head.¶
The two refs share no history. Usually the base branch isn't fetched, or you're comparing across unrelated histories such as an orphan branch.
Fetch the base, or pass --no-merge-base to diff the two refs directly without a common
ancestor.
no ecosystem detected¶
The full message names all three fixes:
no ecosystem detected, add a marker file, pass --ecosystem, or supply --coverage-file
to ingest a pre-made report.
Detection looks for pyproject.toml, package.json, *.sln / *.csproj, or pom.xml in
the repo root. Two cases surprise people:
- A bare
package.jsonwith no test script and no jest or vitest config is deliberately not detected, so Brimyr doesn't runjestagainst a repo that only ships frontend assets. - Gradle is recognised as Java but not auto-detected, because the built-in command is
mvn. Passecosystem: 'java'with your owntest_command.
The tests ran but the build is red with exit 2¶
That's the broken-run rule. The suite failed, produced no coverage file, or wrote something unparseable. Look at the test output above the Brimyr step: the underlying failure is there, and Brimyr is refusing to convert it into a coverage number.
Small pull requests aren't being gated¶
Working as intended. min_lines defaults to 20, so a diff with fewer changed executable
lines than that isn't gated, and the summary says so:
⚪ Only 3 changed executable line(s), below the 20-line minimum, so the 80.0%
threshold was not applied (patch coverage was 33.3%).
Set min_lines: '0' to gate every diff. See Patch coverage.
skipped (sonar-scanner not found on PATH)¶
The scanner install didn't happen or didn't succeed. It only runs when sonar_url is set,
and it's deliberately non-fatal, so the job stays green with no analysis uploaded.
Check that sonar_url is non-empty, and look for an earlier ::warning:: from the install
step. On a container or self-hosted runner without .NET, add actions/setup-dotnet before
Brimyr for .NET repos.
skipped (no token in $SONAR_TOKEN)¶
sonar_url is set but sonar_token is empty. Non-blocking by design, so nothing failed.
Pass the secret, or clear sonar_url if you didn't mean to enable the leg.
skipped (Java / JVM needs sonar.java.binaries)¶
sonar-scanner can't analyse Java without compiled classes. Supply them:
sonar_args: '-Dsonar.java.binaries=**/target/classes'
See SonarQube.
No HTML report artifact appeared¶
html_report needs ReportGenerator, which needs the .NET runtime. On GitHub-hosted runners
the SDK is preinstalled. Elsewhere you'll see:
::warning::html_report needs the .NET runtime, add actions/setup-dotnet
Add actions/setup-dotnet before the Brimyr step. The gate is unaffected either way.
The PR comment isn't posted¶
The comment is failure-isolated, so it never fails the run. Check, in order:
pr_comment: 'true'is set.- The workflow has
pull-requests: write. - The run is on a pull request. There's no PR to comment on for a push.
- The event is
pull_request, notpull_request_target.
Fork pull requests get a read-only token, so the comment can't post. That's a GitHub restriction, not a Brimyr one.
The comment says github-actions[bot] instead of Brimyr[bot]¶
The token broker didn't answer, and the run fell back to the job token. That fallback is
silent and deliberate: the comment still posts, only the byline changes. Check that
token_broker_url is set and the workflow has id-token: write.