fix(cli): prevent handled errors from producing duplicate output [CLI-1765] - #7130
fix(cli): prevent handled errors from producing duplicate output [CLI-1765]#7130bdemeo12 wants to merge 3 commits into
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 PR originates from: #7047 |
125499b to
cf9231f
Compare
|
|
||
| for _, err := range unwrappedErrs { | ||
| var exitErr *exec.ExitError | ||
| if errors.As(err, &exitErr) && exitErr.ExitCode() < constants.SNYK_EXIT_CODE_ERROR { |
There was a problem hiding this comment.
An exit code below SNYK_EXIT_CODE_ERROR means the command worked and anything alongside it is noise
There was a problem hiding this comment.
I found a second reproduction after this PR was opened: running snyk code test can successfully return findings while an auxiliary organization-slug lookup returns 403. The scan returns ErrorWithExitCode{1}, which is then joined with the captured Forbidden error and printed after the results.
This implementation appears to fix the Container case (exec.ExitError{1}), but not this Code case because the early suppression only checks exec.ExitError. Could we determine suppression from the original command error before joining, covering both error types?
| // | ||
| // Joined errors match no direct type assertion, so they are unwrapped and checked | ||
| // one at a time. | ||
| func shouldSuppressDisplay(err error) bool { |
There was a problem hiding this comment.
All errors are combined here:
Line 771 in cbc275e
shouldSuppressDisplay returns true when there's nothing to show:
Line 504 in cbc275e
Previously displayError checked the error's type directly. it didnt account for a combined error, so it fell through to the printing code and emitted a second JSON object.:
Line 462 in 26f0543
Now this check suppresses the display err by returning early, before the printing
There was a problem hiding this comment.
The Code reproduction also makes me think the original error is the better place to make this decision. Before processError, we know whether the command returned exec.ExitError or ErrorWithExitCode; after joining, we have to infer that recursively and risk suppressing unrelated sibling errors.
There was a problem hiding this comment.
I think we could calculate suppression from the original error in tearDown func, before processError joins anything?
suppressDisplay := shouldSuppressDisplay(err)
if err != nil {
allErrors, outputError = processError(err, errorList)
// If processError selected a different error, such as maintenance or the
// captured cause of TS_CLI_TERMINATED, that error still needs displaying.
suppressDisplay = suppressDisplay && errors.Is(outputError, err)
}
if !suppressDisplay {
displayError(outputError, ...)
}shouldSuppressDisplay could then retain the simple direct checks for exec.ExitError, ErrorWithExitCode, and already-displayed errors. This covers both cases while avoiding recursive unwrapping.
668cf76 to
bc48bab
Compare
| "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.
bumped SDP to use as an acceptance test
Errors are joined before they reach displayError, and a joined error matched none of its direct type assertions, so an already-handled error was printed after the command's real output - emitting a second JSON object and breaking JSON.parse(stdout). displayError now unwraps joined errors and checks them one at a time. Pins snyk-docker-plugin to 9.20.0 so CI exercises the failure being fixed. CLI-1765 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bc48bab to
33b1438
Compare
This comment has been minimized.
This comment has been minimized.
…765] Evaluate suppression on the original command error (exec.ExitError or ErrorWithExitCode, exit code < SNYK_EXIT_CODE_ERROR) instead of the joined error, covering the snyk code test case, and guard with errors.Is so a promoted error is still displayed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This comment has been minimized.
This comment has been minimized.
PR Reviewer Guide 🔍
|
Pull Request Submission Checklist
are release-note ready, emphasizing
what was changed, not how.
What does this PR do?
Fixes:
json output being unparseable because an extra error was printed after the result. This happens when the CLI receives an error (aside from exit status 1), it prints it — so once teardown joins the exit error together with the handled network errors, the result is no longer exit status 1 and gets printed after the real output.
Also pins
snyk-docker-pluginto9.20.0so CI exercises the failure being fixed. (Original PR: #7047 + discussion: https://snyksec.atlassian.net/servicedesk/customer/portal/64/CLIA-1576)Where should the reviewer start?
cliv2/pkg/core/main.go—shouldSuppressDisplayand the third return value fromprocessError, then the guardeddisplayErrorcall intearDown.Then
cliv2/pkg/core/main_test.gofor the two new cases inTest_processErrorand the newTest_shouldSuppressDisplay.How should this be manually tested?
Against an image whose provenance fetch fails (SDP version 9.20.0 +):
The
container.spec.tsacceptance job is the automated equivalent — it fails on 9.20.0 without this fix, which is why the pin is included here.What's the product update that needs to be communicated to CLI users?
n/a