SonarQube Pull Request Decoration: Inline Code Analysis on Every PR
SonarQube PR decoration posts inline comments directly on changed lines in pull requests and adds a quality gate status check. Reviewers see new bugs and vulnerabilities without leaving the PR review interface.
Key Takeaways
PR decoration is separate from the main branch analysis. SonarQube analyzes only changed code in a PR, then compares against the target branch baseline.
Each ALM (GitHub/GitLab/Bitbucket) needs its own connection. Configure an ALM integration in SonarQube Administration before decoration works.
The sonar.pullrequest.* parameters tell SonarQube it's a PR analysis. Pass these in your CI configuration or sonar-project.properties.
Quality gate on PR only covers new code. Issues in existing code don't fail the PR quality gate (unless you configure otherwise).
Branch analysis requires Developer Edition or higher. Community Edition supports PR decoration only on the main branch.
How PR Decoration Works
- CI runs SonarQube analysis on the PR branch
- SonarQube compares changed code against the target branch baseline
- SonarQube posts a status check to the PR (pass/fail based on quality gate)
- SonarQube adds inline comments on lines with new issues
- Reviewers see issues without switching to the SonarQube UI
GitHub Integration Setup
Step 1: Create a GitHub App or Personal Access Token
For GitHub Apps (recommended for organizations):
- In GitHub, go to Settings → Developer settings → GitHub Apps → New GitHub App
- Set permissions: Pull requests (Read & Write), Commit statuses (Read & Write)
- Generate a private key and download it
For Personal Access Token (simpler for small teams):
- GitHub → Settings → Developer settings → Personal access tokens
- Required scopes:
repo(full),read:org
Step 2: Configure ALM in SonarQube
Administration → ALM Integrations → GitHub:
Configuration name: GitHub
App ID: <your-github-app-id>
Client ID: <oauth-client-id>
Client Secret: <oauth-client-secret>
Private Key: <contents-of-private-key.pem>Or for PAT-based setup:
Personal Access Token: <your-token>Step 3: Link Your Project to the GitHub Repository
In your SonarQube project → Project Settings → General Settings → ALM Integration:
- ALM: GitHub
- Repository name:
owner/repo-name
Step 4: Configure Your CI
GitHub Actions example:
name: SonarQube Analysis
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for SonarQube blame analysis
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
with:
args: >
-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}
-Dsonar.pullrequest.branch=${{ github.head_ref }}
-Dsonar.pullrequest.base=${{ github.base_ref }}
-Dsonar.pullrequest.github.repository=${{ github.repository }}Step 5: Verify
Open a pull request. Within a few minutes:
- A status check appears on the PR (SonarQube/Quality Gate)
- Inline comments appear on lines with new issues
- A summary comment shows issue count, coverage delta, and quality gate result
GitLab Integration
Create a GitLab Access Token
Settings → Access Tokens → Create token with api scope.
Configure in SonarQube
Administration → ALM Integrations → GitLab:
Configuration name: GitLab
GitLab URL: https://gitlab.com (or your self-hosted instance)
Personal Access Token: <your-token>CI Configuration (GitLab CI)
sonarqube-check:
stage: test
image:
name: sonarsource/sonar-scanner-cli:latest
entrypoint: [""]
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
GIT_DEPTH: "0"
script:
- sonar-scanner
-Dsonar.projectKey=${CI_PROJECT_PATH_SLUG}
-Dsonar.sources=.
-Dsonar.host.url=${SONAR_HOST_URL}
-Dsonar.login=${SONAR_TOKEN}
-Dsonar.pullrequest.key=${CI_MERGE_REQUEST_IID}
-Dsonar.pullrequest.branch=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}
-Dsonar.pullrequest.base=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
only:
- merge_requestsConfiguring What Gets Decorated
Only Fail on New Issues
The default behavior is correct: only new code (introduced in the PR) is evaluated. Don't change this unless you have a specific reason.
# sonar-project.properties
sonar.qualitygate.wait=true # Fail the CI build if quality gate failsCustomize the Quality Gate for PRs
Create a separate quality gate for PRs in Administration → Quality Gates:
- Conditions: Only on new code
- Coverage on new code ≥ 80%
- Duplicated lines on new code < 3%
- New bugs = 0
- New vulnerabilities = 0
Assign this quality gate to your project.
Suppress Decoration Noise
If you're getting too many inline comments from legacy code in the PR:
# Exclude test files from new code policy
sonar.coverage.exclusions=**/*Test.java,**/*Spec.js
sonar.cpd.exclusions=**/*Test.javaTroubleshooting
Status check doesn't appear: Check that sonar.pullrequest.key is set correctly. For GitHub, this should be the PR number (integer).
Comments appear on wrong lines: Use fetch-depth: 0 in your checkout step. SonarQube needs full git history for accurate blame analysis.
Quality gate always passes: Check if you're using Community Edition (which doesn't support branch analysis). The quality gate applies to the full project, not just the PR diff.
Decoration works but coverage delta is wrong: Ensure coverage reports are being imported with sonar.coverage.jacoco.xmlReportPaths (Java) or sonar.javascript.lcov.reportPaths (JS).
PR decoration turns SonarQube from a dashboard you check occasionally into a gatekeeper you can't ignore. New issues surface in the review workflow, before they reach the main branch.