For safeguard scan, 0 means the scan ran regardless of findings, 1 means the command failed, and 2 means results were written but the vulnerability scan did not run. For safeguard detect, 0 means the scan ran, and non-zero means the command itself failed.
The table, in words
Scan returns 0 when the scan ran, whether or not it found anything. It returns 1 when the command failed, for example a bad argument. It returns 2 when output files were written but the vulnerability scan did not run. That last case is the one to watch in CI.
Detect returns 0 when the scan ran. The documentation gives an invalid detect type value as an example of a failure that returns non-zero. The CLI usage page says exit codes for detect are not documented in more detail, so do not build logic on a specific non-zero number.
Why code 2 matters
Code 2 is a half success. You get an SBOM and other files, so the output folder looks normal, but the matching step against vulnerability data did not happen. If your pipeline treats only 1 as an error, a run that skipped vulnerability matching looks green and the empty vulnerability file looks like good news. That is a false pass.
The usual cause on a fresh runner is missing scanners, covered in the syft and grype post. The fix is to install them or point the tools directory variable at them.
A safe handling pattern
In shell, capture the code rather than letting the step fail blindly:
safeguard scan --dir . --output out/
code=$?
if [ "$code" -ne 0 ]; then
echo "safeguard scan exited with $code"
exit "$code"
fi
This fails on both 1 and 2, which is what most teams want. If a CI system runs your script with errexit on, the first line will already stop on a non-zero code, and you can drop the wrapper.
What the code does not tell you
Neither command encodes severity in its exit status. A zero from either one says only that it ran. Gating on severity needs a policy gate or a check on the result file, as set out in how to gate a build.
Other commands
The usage page documents codes for a few others, such as sif verify and doctor. Doctor exits zero whenever its checks ran, so read its output, not its status. Exit codes for validate are not documented on that page.
Testing your handling
Do not trust the table in a blog post, including this one. Run the command in three situations on a scratch branch: a normal repository, a runner without the scanners, and an invalid argument. Record the exit code each time and check that your pipeline reacts the way you intend. It takes ten minutes and replaces guesswork.
Quick check
My scan exited 2 but the output folder exists. Is that a pass?
No. The files were written but vulnerability matching did not run. Fix the cause, usually missing scanners, and rerun.
Does a critical finding change the exit code?
No. Findings are handled by platform policy, not by the exit status.
Sources
Last reviewed: 19 October 2026.