Safeguard
Developer Tools•

What exit codes should a security CLI return in CI?

Use distinct exit codes for clean, findings found and tool error, so a pipeline never mistakes a crash for a pass. A short design guide for CLI authors.

Safeguard Security Team
4 min read

Return at least three distinct exit codes: success when nothing was found, a specific non-zero code when the scan ran and found issues above the threshold, and a different non-zero code when the tool itself failed. Without the split, a crash can look like a pass or a finding.

Why it matters

CI systems decide pass or fail from one number. If your tool returns zero for both a clean scan and a scan that could not reach its data source, the pipeline goes green on a scan that never happened. If it returns one for both findings and a crash, teams cannot tell a real issue from a broken tool, and they learn to ignore failures.

A simple scheme

CodeMeaningPipeline should
0Scan completed, nothing at or above the thresholdPass
1Scan completed, findings at or above the thresholdFail, show findings
2Tool error, bad arguments or unreadable inputFail, show the error
3Could not reach the service or data sourceFail or retry, by policy

The exact numbers matter less than documenting them and keeping them stable. Changing a code in a minor release breaks every pipeline that depends on it.

Design rules

  1. Never exit zero on an incomplete scan. If only half the files were readable, say so and use an error code, or require an explicit flag to accept partial results.
  2. Make the threshold a flag. Users choose the severity level that fails the build, and the default should be written down.
  3. Print the reason, not just the code. A final line stating the code, the count of findings and the threshold makes logs easy to read.
  4. Support a report-only mode. Teams adopting a gate want to see results before enforcing. In that mode, return zero on findings but still non-zero on tool errors.
  5. Separate output formats from exit codes. A JSON report on disk should not change the exit status.
  6. Test the codes. Add tests that run your tool against a clean fixture, a vulnerable fixture, a missing file and a blocked network.

Think about timeouts

A hung network call that never returns is worse than a failure, because it holds a CI runner. Set a timeout, and return the service error code when it fires. Let users raise the limit with a flag.

Gates in the pull request

Exit codes are what let a pull request gate work. When a check returns a clear pass or fail, the platform can block a merge on it. The demo shows the idea from the developer's side.

Try it yourself

Open the demo and watch a pull request check go from pass to blocked. Notice that the reason is shown next to the status.

Click through it at your own pace, or open it full screen.

Quick check

Should a scan with findings and a scan that crashed use the same code?

No. Separate codes let teams respond correctly, and stop a crash being read as a pass.

Is it safe to change an exit code in a minor release?

Avoid it. Pipelines depend on the numbers. Treat changes as breaking and announce them.

Safeguard's policies and gates can block a pull request on defined conditions, and you can try a free scan at gold.safeguard.sh/scan.

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.