Adopt Strict Quality Gates on Legacy Code with Qodana Baselines
Use Qodana baselines to suppress existing violations and fail CI only on new issues. Worked qodana.yaml config, baseline generation commands, GitHub Actions integration, and common pitfalls like line-sensitivity and inspection-ID drift.
29 Apr 2026, 20:09 UTC

The problem: you want strict CI gates but the codebase has thousands of existing violations
Turning on Qodana in a mature repository usually means the first scan fails with hundreds or thousands of findings. Blocking every pull request on that backlog stalls development. The practical alternative is to suppress all current violations once, then fail the pipeline only when new issues appear. Qodana baselines do exactly this: they record the existing violation set in a SARIF file, and subsequent scans compare against that baseline.
How the baseline mechanism works
A baseline is a qodana.sarif.json file that stores, for each finding, the inspection ID, file path, line number, and a hash of the inspection message. On each scan, Qodana computes the same tuple for every detected issue. If the tuple exists in the baseline, the issue is treated as suppressed and does not cause a non-zero exit. Any tuple not in the baseline is reported as new and fails the quality gate.
Because matching is line-sensitive, moving a block of code up or down without changing its logic will produce a different line number and therefore a "new" violation. Large refactors require a baseline refresh (see Regenerating after refactors).
Worked configuration: qodana.yaml and baseline generation
Assume a typical Java/Kotlin project. First, create or edit qodana.yaml at the repository root:
version: "1.0"
inspection:
baseline: qodana.sarif.json
includePaths:
- src/**
excludePaths:
- "**/generated/**"
- "**/build/**"
profile:
name: qodana.recommended
The baseline key points to the SARIF file that will be committed alongside the config. includePaths and excludePaths keep the scan focused on source code and avoid churn from generated artifacts — prefer this over suppressing build output in the baseline.
Generating the initial baseline locally
Run the scan in a container with the project mounted. Use the same Qodana version you will pin in CI (here 2024.3). The --save-baseline flag writes qodana.sarif.json to the working directory.
docker run --rm \
-v $(pwd):/data \
jetbrains/qodana-community:2024.3 \
scan --baseline qodana.sarif.json --save-baseline
- Where to run: any machine with Docker (developer laptop, CI agent).
- Permissions: read/write access to the repository directory.
- Expected check: the command exits 0 and creates
qodana.sarif.json(typically 100 KB – several MB). - Risk: the baseline captures all current violations, including potential security or correctness issues. Review before committing (see Auditing the baseline).
Auditing the baseline before commit
Open qodana.sarif.json in VS Code with the SARIF Viewer extension or in a JetBrains IDE (Analyze → View SARIF). Filter by severity or inspection ID (e.g., SerializableHasSerialVersionUIDField, UnusedDeclaration) to decide whether any suppressed finding should be fixed instead of baselined. Commit only after this review.
CI/CD integration: GitHub Actions example
Add a workflow that runs on pull requests. Pin the action version to avoid inspection-ID drift across Qodana releases.
name: Qodana Scan
on: [pull_request]
jobs:
qodana:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Qodana scan with baseline
uses: jetbrains/qodana-action@v2024.3
with:
args: "scan --baseline qodana.sarif.json"
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} # required for paid profiles only
The action exits non-zero only when new violations appear. Existing violations recorded in the baseline are reported but do not fail the job. This lets you merge PRs while tracking legacy debt separately.
Regenerating after large refactors
When a refactor moves code across lines (e.g., extracting a method, reformatting), the baseline will flag the moved lines as new violations. Regenerate the baseline with the same command used initially:
docker run --rm \
-v $(pwd):/data \
jetbrains/qodana-community:2024.3 \
scan --baseline qodana.sarif.json --save-baseline
Commit the updated qodana.sarif.json in a dedicated PR so the change is reviewable. Do not regenerate on every CI run — that would defeat the purpose.
Common mistakes and limits
1. Committing a baseline without review
A baseline silently accepts every finding it contains. If a critical security inspection (e.g., HardcodedPassword) is in the baseline, you lose the gate for that issue. Always audit the SARIF before the first commit and after each regeneration.
2. Baseline bloat in monorepos
Baselines are per-project. In a monorepo, a single baseline covering all modules mixes unrelated violations and grows quickly. Options:
- One baseline per subproject, each with its own
qodana.yamlandqodana.sarif.json. - A combined baseline with explicit
includePaths/excludePathsper module in a rootqodana.yaml.
If the SARIF file exceeds ~50 MB, store it in Git LFS or as a CI artifact rather than in the main repo history.
3. Inspection ID changes across Qodana versions
JetBrains occasionally renames or retires inspections. A baseline created with 2024.3 may not match findings from 2025.1. Pin the version in CI (jetbrains/qodana-action@v2024.3) and regenerate the baseline after any intentional upgrade.
4. Suppressing generated code instead of excluding it
Build outputs, protobuf-generated sources, and similar files change on every build. Adding them to the baseline causes constant churn. Use excludePaths in qodana.yaml (as shown above) to keep them out of the scan entirely.
Verification checklist
- Generate baseline locally with the pinned Docker image; confirm
qodana.sarif.jsonexists and is non-empty. - Open the SARIF in VS Code or JetBrains IDE; verify no critical findings are suppressed unintentionally.
- Commit
qodana.yamlandqodana.sarif.jsonto a feature branch. - Push and watch the GitHub Actions run: it should pass (exit 0) with the current codebase.
- Introduce a deliberate new violation (e.g., add an unused variable) in a follow-up commit; the same workflow should fail.
- Confirm the failure message references the new inspection ID and line number, not a baseline entry.
When baselines are not the right tool
- Greenfield projects: start with a clean baseline (empty SARIF) and fix issues as they appear.
- Security-first policies: if certain inspection categories (injection, crypto misuse) must never be suppressed, run a separate scan with a strict profile that ignores the baseline.
- Non-JetBrains ecosystems: SARIF is portable, but the baseline workflow is tied to Qodana's inspection IDs. Teams using SonarQube or GitHub Code Scanning natively may prefer their own baseline mechanisms.
Summary
Qodana baselines let you enforce a "no new warnings" policy immediately on a legacy codebase. The workflow is: generate a SARIF baseline once, commit it alongside qodana.yaml, pin the Qodana version in CI, and audit the baseline before every regeneration. The gate fails only on regressions, giving you a practical path to improve quality without halting delivery.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.