← All posts

Testing

SonarQube: Catch AI-Generated Code Smells Before Production

SonarQube's static analysis catches the subtle bugs and code smells AI assistants routinely introduce before they ship.

Magnifying glass scanning layered PHP code files and surfacing code smell and vulnerability indicators in a blue indigo editorial style

AI coding assistants have made me faster. I will admit that without hesitation. But I have also watched them introduce a very specific category of bug: code that looks correct at a glance, passes a quick manual review, and then quietly misbehaves in production. Null-pointer traps hiding behind fluent chaining. Dead code branches that inflate bundle size. Security hotspots dressed up in modern syntax. This is the AI code smell detection problem, and it is real. The tool that earned a permanent spot in my knowledge base is SonarQube Community Edition.

What SonarQube Actually Is

SonarQube is a self-hosted static analysis platform. You run a server using a Docker image, point the scanner at your repo, and it returns a detailed report covering bugs, vulnerabilities, security hotspots, code smells, duplications, and test coverage gaps. The Community Edition is GPL-3.0 licensed and completely free. It supports over 30 languages out of the box. For PHP-heavy backend teams the rules engine is deep and the defaults are well-tuned. You do not need to configure much to get useful signal immediately.

Why AI-Generated Code Specifically Needs This

Here is the pattern I kept seeing: an AI assistant generates a function that solves the immediate problem correctly. But it also introduces a subtle issue one or two layers away. An unhandled exception. A variable that shadows an outer scope. A var_dump left in a hot path. Or scarier still, a string-interpolated query that is one user input away from an injection vector. These are not hallucinations. The code runs. It just carries technical debt or a latent vulnerability that only surfaces later.

Human reviewers miss these because AI output reads confidently. The style is clean. The naming is sensible. We pattern-match on "this looks right" and move on. SonarQube does not pattern-match on aesthetics. It parses the AST, traces data flow, and applies rules mechanically. That is exactly the kind of reviewer AI-generated code needs as a second pass.

My Maennche Setup: GitHub Actions and PHPUnit

When I was lead PHP developer at Maennche, I wired SonarQube directly into our GitHub Actions CI/CD pipeline. The workflow was straightforward but strict. Code lands on a feature branch, GitHub Actions spins up, runs PHPUnit with coverage, then triggers the SonarQube scanner. Only if the quality gate passed would the pipeline proceed to deployment. No exceptions.

The sonar-project.properties file at the repo root looked roughly like this:

sonar.projectKey=maennche-api sonar.sources=src sonar.tests=tests sonar.php.coverage.reportPaths=coverage.xml sonar.exclusions=vendor/** 

And the GitHub Actions step that called the scanner was straightforward:

- name: Run PHPUnit with coverage run: vendor/bin/phpunit --coverage-clover=coverage.xml - name: SonarQube Scan uses: SonarSource/sonarqube-scan-action@v4 env: SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 

PHPUnit ran first. Coverage output was written to coverage.xml. The scanner picked that up, reported coverage alongside all the static analysis findings, and the quality gate either passed or failed the entire pipeline. If it failed, the deployment job never ran. That sequence gave us confidence that what reached staging had at least passed a mechanical quality bar.

Quality Gates: The Feature That Changes Behavior

The thing that moves SonarQube from an interesting report to actual pipeline enforcement is the Quality Gate. You define thresholds: zero new critical bugs, coverage on new code above 80 percent, no new security hotspots left unreviewed. The scanner exits with a non-zero code if the thresholds are not met. Wire that exit code into your CI workflow and you have made passing SonarQube a hard requirement before merge.

In practice the gate forces you to address mechanical debt before it compounds. Human review time goes toward logic, architecture, and intent. The things static analysis cannot judge. The gate handled the mechanical layer so we did not have to.

Rough Edges and Honest Limitations

  • Community Edition is single-branch only. You cannot scan pull request branches and compare them to main without upgrading to the paid Developer Edition. An unofficial community branch plugin exists but it lags behind SonarQube releases occasionally.
  • Java runtime required. The scanner is a JVM process. It adds complexity to lean Alpine-based CI images and bumps memory usage slightly.
  • False positives exist. The PHP taint-analysis rules occasionally flag patterns that are intentional or safely handled downstream. You will add NOSONAR comments or configure exclusions over time.
  • The dashboard is dense. Spend 20 minutes learning the difference between Bugs, Vulnerabilities, and Security Hotspots and it clicks. Until then it feels like a lot.
  • No LLM-specific ruleset yet. SonarQube does not label issues as AI-introduced. You are applying general static analysis to AI-generated code, which still works well, but a ruleset tuned specifically to common LLM output patterns would be a meaningful improvement.

Bottom Line

The rise of AI code generation has not made static analysis less relevant. It has made it more necessary. SonarQube is not a marketing concept. It is rigorous AST-level and data-flow analysis applied to code that humans are reviewing less carefully than they used to. I used it at Maennche, it caught real problems that human review missed, and I still recommend it to any team shipping AI-assisted code without a quality gate in place. The Community Edition is free, self-hosted, and genuinely solid. Relying entirely on human eyes to catch mechanical debt is a bet that does not pay off long term.