Using Qodana Baseline to Stop New Issues Without Drowning in Legacy Debt
Capture a one‑time Qodana baseline to silence legacy noise, then fail CI only on new issues. Learn the exact Docker commands, CI snippet, and safe baseline‑update workflow.
30 Mar 2026, 10:56 UTC

The problem: legacy noise drowns out new regressions
Static analysis is only useful when the signal you care about—new bugs, style violations, security smells—is louder than the background of pre‑existing issues. In a mature codebase the first Qodana run can report hundreds of findings, most of which are already known and deliberately deferred. If you fail the build on that raw output, every pull request blocks; if you ignore it, you lose the safety net for future changes.
Takeaway: Capture a one‑time snapshot of the current issue set as a baseline, then configure CI to fail only when the analysis finds issues that are not in that snapshot. This gives you an incremental quality gate without a massive refactor up front.
Creating the baseline
Run Qodana locally (or in a throw‑away CI job) against the full repository. The command writes a .qodana/baseline.yaml file that records each issue’s inspection ID, file path, line range, and severity.
# Run from the repository root; requires Docker and read access to source files
$ docker run --rm -v "$(pwd):/data" \
-e QODANA_TOKEN="${QODANA_TOKEN}" \
jetbrains/qodana-java:2024.2 \
analyze --baseline --save-report
After the run you will see a file like:
.qodana/baseline.yaml
---
- inspection: "UnusedImport"
file: "src/main/java/com/example/Util.java"
line: 12
severity: "WARNING"
- inspection: "NullableProblems"
file: "src/main/java/com/example/Service.java"
line: 45
severity: "ERROR"
...
Commit this file. It becomes the contract: any future run that produces the same issue at the same location and severity is considered “already known” and will not break the build.
Integrating the baseline into CI
In your pipeline (GitHub Actions, GitLab CI, Azure Pipelines, etc.) add a single step that runs qodana analyze with the baseline present. The container mounts the repo, reads .qodana/baseline.yaml, and exits with a non‑zero code only when new issues appear.
# .github/workflows/qodana.yml (GitHub Actions example)
name: Qodana
on: [push, pull_request]
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # for SARIF upload if desired
steps:
- uses: actions/checkout@v4
- name: Run Qodana with baseline
run: |
docker run --rm -v "${{ github.workspace }}:/data" \
-e QODANA_TOKEN="${{ secrets.QODANA_TOKEN }}" \
jetbrains/qodana-java:2024.2 \
analyze --fail-threshold=ERROR
Key flags:
--fail-threshold=ERROR– fail only on errors; warnings are reported but do not block.--baselineis implicit when.qodana/baseline.yamlexists.
Result: a pull request that introduces a new NullableProblems error in a previously clean file will cause the job to fail, while the existing unused import in Util.java stays silent.
Updating the baseline safely
When you deliberately fix a batch of known issues or decide to accept a new class of warnings, you must refresh the baseline explicitly. This prevents accidental “baseline drift” where a developer runs qodana analyze locally, sees a green build, and unintentionally adds new issues to the baseline.
# Run locally after you have resolved the issues you want to accept
$ docker run --rm -v "$(pwd):/data" \
-e QODANA_TOKEN="${QODANA_TOKEN}" \
jetbrains/qodana-java:2024.2 \
baseline update
The command rewrites .qodana/baseline.yaml with the current issue set. Commit the updated file and push; the next CI run will treat the new snapshot as the baseline.
Guardrail: Add a CODEOWNERS rule or branch protection that requires a review for changes to .qodana/baseline.yaml. This forces a human to verify that the baseline change is intentional.
Trade‑offs and practical checks
| Aspect | Benefit | Limitation |
|---|---|---|
| Incremental enforcement | Only new regressions break the build. | Existing issues stay hidden; they can accumulate if never revisited. |
| Docker‑based scan | Consistent environment, no host‑toolchain setup. | Adds ~30‑90 s per run; cache the Docker image and .qodana directory to mitigate. |
| Language coverage | Java, Kotlin, Python, JavaScript, Go, PHP, etc., share the same baseline format. | Inspection depth varies; older languages (e.g., legacy C++) have fewer checks, so baseline may be sparse. |
Verification checklist after each CI run
- Open the generated HTML report (artifact or
qodana.sarif.html) and confirm the “New issues” section lists only the changes you expect. - Run
qodana analyze --dry-runlocally on the same commit; the exit code should match CI. - Periodically (e.g., quarterly) run
qodana analyze --baselinewithout--fail-thresholdto see the total issue count; if it grows unchecked, schedule a baseline review sprint.
Closing action
Add the baseline file to version control today, wire the single Docker command into your CI, and protect the baseline file with a review gate. You’ll get immediate feedback on new problems while buying time to pay down the legacy debt on your own schedule.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.