fix(cli): classify non-failure exit codes in tearDown to prevent duplicate output [CLI-1765] - #7191
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
This comment has been minimized.
This comment has been minimized.
cf98bdc to
b82c8b3
Compare
This comment has been minimized.
This comment has been minimized.
b82c8b3 to
836ee0d
Compare
PR Reviewer Guide 🔍
|
both findings are intentional changes to fix the bug where non failures caused an error message to be printed. |
| if exitErr, ok := err.(*exec.ExitError); ok { | ||
| return !isNonFailureExitCode(exitErr.ExitCode()) | ||
| } | ||
| if exitCodeErr, ok := err.(*ErrorWithExitCode); ok { | ||
| return !isNonFailureExitCode(exitCodeErr.ExitCode) | ||
| } |
There was a problem hiding this comment.
Here you are testing for ExitError and ErrorWithExitCode types, how do we guarantee we are addressing all possible types in this function? (e.g. suppose tomorrow we have ErrorWithExitCode2...)
I would suggest having a separate function for extracting this ExitCode based on the type, if possible
There was a problem hiding this comment.
not sure if it is worth yet to do so. For now It probably is small enough.
| func isNonFailureExitCode(code int) bool { | ||
| switch code { | ||
| case constants.SNYK_EXIT_CODE_VULNERABILITIES_FOUND, | ||
| constants.SNYK_EXIT_CODE_UNSUPPORTED_PROJECTS: | ||
| return true | ||
| default: | ||
| return false | ||
| } | ||
| } |
There was a problem hiding this comment.
Suggestion: change the name of this function to reflect the returned value itself, e.g. isSuccessfulCode, sounds much better. (although I know to call SNYK_EXIT_CODE_UNSUPPORTED_PROJECTS as successful is debatable - wondering about when have no vulns found, is there any separate code?)
Reading a "not" / "non" in a function name is counter-intuitive
| "snyk-config": "^5.0.0", | ||
| "snyk-cpp-plugin": "^2.24.3", | ||
| "snyk-docker-plugin": "9.19.0", | ||
| "snyk-docker-plugin": "9.20.0", |
There was a problem hiding this comment.
here you are bumping snyk-docker-plugin, is there anything to fill in the PR description for the release automation about this change in "What's the product update that needs to be communicated to CLI users?"?
There was a problem hiding this comment.
I will add this in a follow based on the teams feedback.
danskmt
left a comment
There was a problem hiding this comment.
Left some suggestions, mostly about naming. LGTM
Pull Request Submission Checklist
are release-note ready, emphasizing
what was changed, not how.
What does this PR do?
CLI Ticket: https://snyksec.atlassian.net/browse/CLI-1765
Problem
When a CLI command completes successfully but with a non-zero exit code (e.g. exit 1 = vulnerabilities found), and an auxiliary network error was handled during the run (e.g. a provenance fetch failure in
snyk-docker-plugin9.20.0),tearDownfeeds both errors intoprocessError. InsideprocessError,FindMostRelevantErrorjoins them viaerrors.Join, producing a new error whose concrete type is neither*exec.ExitErrornor*ErrorWithExitCode. The existing guard indisplayErroruses direct type assertions, so it fails to recognise the joined error as a command result and prints it after the command's own JSON output — making the output unparseable.Root cause
The condition
if err != nilintearDown(line 519) treats every non-nil error the same. But Go'serrorinterface is used for two distinct purposes here:*exec.ExitError(exit 1, 3) or*ErrorWithExitCodethat carry a known non-failure exit code. The command already produced its output.Sending command results through
processError→FindMostRelevantError→errors.Joindestroys the type information that the rest of the pipeline depends on.Fix
Replace
if err != nilwithif cli_errors.IsFailure(err).IsFailurechecks both the error type and the specific exit code:*exec.ExitErroror*ErrorWithExitCodewith exit code 1 (vulnerabilities found) or 3 (unsupported projects) → not a failure, skipprocessError, preserve the original type*exec.ExitErrorwith exit code 44 (TS CLI terminated) → is a failure, goes throughprocessErrorwhere the existing terminate filter handles itThis also pins
snyk-docker-pluginto9.20.0, which is the version that triggers the auxiliary network errors that exposed this bug.Where should the reviewer start?
cliv2/internal/errors/errors.go—IsFailureandisNonFailureExitCode, then the one-line change incliv2/pkg/core/main.gotearDown.Then
cliv2/internal/errors/errors_test.go—TestIsFailurecovers nil, exit codes 1/2/3/44,ErrorWithExitCode, plain errors, and joined errors.How should this be manually tested?
Against an image whose provenance fetch fails (snyk-docker-plugin 9.20.0+):
With the fix,
out.jsonshould be valid JSON with no trailing error text. Without the fix, an extra error line is appended after the JSON.What's the product update that needs to be communicated to CLI users?
n/a — this fixes an internal error-handling issue; no user-facing behavior change beyond the bug fix.
Risk assessment (Low | Medium | High)?
Low — the change gates a single
ifcondition intearDownwith a well-defined predicate. Command results with exit codes 1 and 3 skipprocessError(which was a no-op for them in the single-error case anyway). All other error paths are unchanged. Exit code 44 (TS CLI terminated) intentionally remains classified as a failure so the existing filter inprocessErrorcontinues to handle it.Any background context you want to provide?
Alternative to #7130, which patches the same bug inside
processError(short-circuit when command owns output) and adds ashouldSuppressDisplayguard beforedisplayError. This PR instead fixes it at the call site — command results never enter the error processing pipeline, soerrors.Joinnever destroys the type, anddisplayError's existing guard works unchanged.What are the relevant tickets?