Bring your own hook: a 5-line PostToolUse template calling sonar analyze agentic
sonar integrate claude will set up an Agentic Analysis (one of the Vortex’s capability) hook for you. That is the right call for most people. But sometimes you want to own the hook yourself: to scope it to a subset of files, to decide exactly when it blocks, to check it into a repo your teammates share, or just to understand what is running before you trust it. This post is the smallest hook that does the job. Five lines, one command, and your agent starts getting CI-grade feedback on every edit.
One naming note up front. The CLI subcommand is sonar analyze agentic; sonar verify is an alias, and there is no sqaa subcommand (the installed hook is just namedsonar-sqaa). As of CLI 0.14.x, sonar analyze has secrets, dependency-risks, and agentic. When in doubt: sonar analyze agentic --help.
What do you need before setting up a Claude Code SonarQube hook?
A SonarQube Cloud project with the Vortex entitlement. Vortex is SonarQube Cloud only and is a paid add-on.
One prior CI analysis. The project has to have been analyzed in your CI pipeline on a long-lived branch at least once after Vortex was enabled for the org. That CI run is what agentic analysis of Vortex restores as context; without it there is nothing to analyze against.
jq on your PATH. The hook uses it to read Claude Code's event JSON.
Vortex’s Agentic Analysis runs server-side over HTTPS against SonarQube Cloud, so you do not need Docker for this CLI path.
The template
Two files. First the hook script.
Then wire it to the PostToolUse event for the file-writing tools in .claude/settings.json:
Restart Claude Code so it picks up the new hook. That is the whole thing.
How does each line of the sonar analyze agentic hook script work?
The mechanism only works if you understand two contracts: how Claude Code hands you the event, and how it reads your answer.
Claude Code passes the event as JSON on stdin, not as environment variables. There is no $tool_input_file_path. The variables Claude Code does export are path placeholders - $CLAUDE_PROJECT_DIR, $CLAUDE_PLUGIN_ROOT, and $CLAUDE_PLUGIN_DATA — and none of them carry the event; beyond those, hooks simply inherit the parent environment.
So the first thing every hook does is read stdin and parse it.
jq -r '.tool_input.file_path // empty' reads the event JSON from stdin and pulls out the path of the file Claude just wrote. For Edit and Write that field is tool_input.file_path.
[ -f "$file" ] || exit 0 bails out cleanly if there is no real file to look at (also covers the empty-string case). Exit 0 means "no opinion, carry on."
sonar analyze agentic --depth DEEP --file "$file" --format text runs Agentic Analysis on just that one file, using the restored CI context. It exits 51 when it reports issues and 0 when the file is clean. We capture both the output and the exit code. The --depth DEEP is not decoration: a single --file defaults to STANDARD depth, and only DEEP turns on cross-file analysis — taint analysis, at present. Only the file you pass is uploaded; SonarQube evaluates it against the project as of your last CI analysis with your modified file substituted in. You can pass several files with repeated --file flags, or use --staged to pick up whatever git has staged.
If the exit code is 51, we print the findings to stderr and exit 2. On a PostToolUse hook, exit 2 does not undo the edit (the tool already ran), but it does feed your stderr back to Claude as feedback. Claude reads the findings, fixes the code, edits again, and the hook fires again. That is the loop.
Otherwise exit 0. Nothing to say.
The choice of exit 2 matters and is easy to get wrong. Per the Claude Code hooks reference, on most events only exit code 2 blocks or feeds back; exit code 1 is treated as a non-blocking error and the turn just continues. And for PostToolUse specifically, plain stdout on exit 0 goes to the debug log, not to the model - though a valid JSON object printed on exit 0 is still parsed, which is the escape hatch used below. So if you want the findings in front of Claude, stderr plus exit 2 is the reliable path.
Why this gives you CI-grade feedback and not lint noise
sonar analyze agentic is not a local linter. It ships the changed file to SonarQube Cloud, which restores the dependency graph, type information, and quality profile from your last CI analysis and runs the same engine your pipeline runs, then returns findings in seconds. That is why, at DEEP depth, it can catch things a single-file tool cannot, like a tainted value flowing from a request parameter into a SQL sink. The exit-51 convention gives you a clean, deterministic signal to branch on, with no output parsing and no LLM judge in the middle deciding whether your code is fine.
How do I handle sonar analyze agentic errors and exit codes in a hook?
The five-liner fails open: any exit code that is not 51 or 0 (an auth failure, a transient server error, a bad flag) falls through to exit 0, and the edit sails past unverified. For a real guardrail, branch on all three cases and decide whether an analysis failure should fail closed:
If you would rather not interrupt the turn and instead attach findings as advisory context, drop the exit 2 and return an additionalContext payload on exit 0 instead:
Both are valid; the difference is whether a finding stops Claude to fix now or just rides along as a note. Note that additionalContext is capped at 10,000 characters; anything longer is written to a file and replaced with a path and a short preview. Check the "JSON output" section of the hooks docs for the exact decision-control fields your Claude Code version supports.
How do I test a Claude Code PostToolUse hook with SonarQube?
Ask Claude to write something obviously unsafe in a file in your project:
Claude writes the file, the PostToolUse hook fires, and Agentic Analysis comes back with a SQL injection finding (javasecurity:S3649, the "database queries should not be vulnerable to injection" taint rule). Claude reads it off stderr and offers to switch to a PreparedStatement. If you see that round-trip, your hook is live. You can sanity-check the command on its own too:
Bonus: a commit gate with the same primitive
Same idea, different event. A PreToolUse hook on Bash can refuse a git commit while blocker-severity issues are open. Note the flags: sonar list issues requires --project, and severity filtering is --severities (plural):
When should I use sonar integrate claude instead of a custom hook?
If you do not need custom scoping or blocking behavior, sonar integrate claude --project installs the Vortex’s Agentic Analysis PostToolUse hook (plus secrets-scanning hooks) for you and manages the wiring; re-running it refreshes those artifacts, but updating the CLI binary itself is a separate sonar update, keeps the CLI updated, and manages the wiring. Bring your own hook when you want control over which files, when it blocks, and what your team sees in version control. The primitive is identical either way: a deterministic command at a fixed point in the agent's loop.
How it works
Once you click Generate, Ollama reads this article and crafts 5 comprehension questions. Your answers are graded against the article content — general knowledge won't be enough. Score 70+ to count toward your certificate.
Questions are cached — you'll always get the same 5 for this article.