Blocking Merges on Code Quality: Qodana failureConditions in CI
Qodana's failureConditions in qodana.yaml let you cap problems by severity so the CI job fails and the merge is blocked when limits are exceeded.
10 Sept 2025, 08:26 UTC

Code review alone does not stop low-quality code from landing in the default branch, because reviewers miss things and style checks are easy to ignore. Qodana addresses this with failure conditions: thresholds you declare in qodana.yaml that make the analysis exit with a non-zero status when issue counts exceed your limits, which fails the CI job and blocks the merge in systems like GitHub Actions or GitLab CI.
How failure conditions work
Qodana runs static analysis inside a Docker container and produces a report of problems grouped by severity. The failureConditions section of qodana.yaml tells Qodana to compare those counts against limits you set. If any limit is exceeded, the process exits non-zero. Your CI system treats that as a failed job, and branch protection rules on the pull request prevent merging until the job passes.
Two threshold families are supported in recent Qodana versions:
- Severity thresholds — maximum allowed problems per severity level (critical, high, moderate, low, info) and an
anytotal. - Test coverage thresholds — minimum fresh-code coverage percentages, evaluated when coverage data is provided.
A worked configuration
Place qodana.yaml in the repository root. A conservative starting point that fails on any critical or high-severity problem, tolerates a small number of moderate findings, and ignores low/info counts:
version: "1.0"
profile:
name: qodana.recommended
failureConditions:
severityThresholds:
any: null
critical: 0
high: 0
moderate: 10
low: null
info: null
Setting a threshold to null disables the check for that severity. Setting it to 0 means a single problem at that severity fails the run. Exact key names and supported severities have changed across Qodana releases, so confirm the schema against the documentation for the linter image version you pin — the example above reflects the documented failureConditions.severityThresholds structure, but you should verify it before relying on it in production.
Wiring it into GitHub Actions
JetBrains publishes the official action under the JetBrains organization. A minimal workflow step:
- name: Qodana scan
uses: JetBrains/qodana-action@v2024.2
with:
args: --baseline,qodana.sarif.json
The action reads qodana.yaml from the repository root automatically; you do not need to pass the config path explicitly. Pin the action to a specific release tag rather than a floating branch so analyzer behavior stays reproducible. The --baseline argument shown is optional: it lets you grandfather existing problems so the gate only fails on newly introduced issues, which is the practical way to adopt gates on a legacy codebase. Omit it if you want a clean-slate policy. Check the action's README for the current input names, since they have changed between major releases.
Checking the gate locally
Qodana's CLI command is qodana scan (not analyze). Run it from the repository root; it requires Docker and will pull the linter image on first use:
qodana scan --show-report
This uses the same qodana.yaml and prints whether the failure conditions passed. To match CI exactly, verify two things:
- The linter image tag the CLI pulls matches the image the CI action uses. A version mismatch can change which inspections run and therefore the counts the gate evaluates.
- The same profile (
qodana.recommended,qodana.starter, or a custom inspection profile) is active in both environments.
The scan writes a SARIF report and an HTML report under the results directory; the SARIF file records which problems were counted, so you can diff local and CI artifacts if results disagree.
Limits and common mistakes
- Starting too strict. Setting
critical: 0on a codebase that already has critical findings fails every build immediately. Teams then learn to ignore or bypass the gate. Use a baseline or start with permissive numbers and tighten them as the backlog shrinks. - Analyzer coverage gaps. Each Qodana linter image targets specific languages (JVM, Python, JS/TS, .NET, Go, PHP, C/C++). Code in unsupported languages is simply not analyzed, so a passing gate says nothing about it. Check which linter image your CI uses and what it covers.
- Config drift. Different image versions or environment variables between local runs and CI produce different counts. Pin versions in both places and treat
qodana.yamlchanges like code changes, with review. - Assuming the gate replaces review. Static analysis catches defect patterns and style violations; it does not judge design. A passing gate is a floor, not a quality guarantee.
Tuning over time
Watch how often the gate fails. Failures on nearly every pull request mean thresholds are unrealistic or a noisy inspection should be disabled in the profile rather than worked around. A gate that never fails may be too loose to matter. Adjust one threshold at a time so you can attribute changes in failure rate to a specific tuning decision.
Before merging the initial qodana.yaml, run qodana scan on a feature branch and confirm the reported counts and the pass/fail verdict match what you expect from the thresholds you wrote.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.