Skip to content

fix(cli): prevent handled errors from producing duplicate output [CLI-1765] - #7130

Draft
bdemeo12 wants to merge 7 commits into
mainfrom
CLI-1765/prevent-duplicate-error-output
Draft

fix(cli): prevent handled errors from producing duplicate output [CLI-1765]#7130
bdemeo12 wants to merge 7 commits into
mainfrom
CLI-1765/prevent-duplicate-error-output

Conversation

@bdemeo12

@bdemeo12 bdemeo12 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Pull Request Submission Checklist

  • Follows CONTRIBUTING guidelines
  • Commit messages
    are release-note ready, emphasizing
    what was changed, not how.
  • Includes detailed description of changes
  • Contains risk assessment (Low | Medium | High)
  • Highlights breaking API changes (if applicable) — none
  • Links to automated tests covering new functionality
  • Includes manual testing instructions (if necessary)
  • Updates relevant GitBook documentation (PR link: ___) — n/a, no user-facing docs change
  • Includes product update to be announced in the next stable release notes

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-plugin to 9.20.0 so 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.goshouldSuppressDisplay and the third return value from processError, then the guarded displayError call in tearDown.

Then cliv2/pkg/core/main_test.go for the two new cases in Test_processError and the new Test_shouldSuppressDisplay.

How should this be manually tested?

Against an image whose provenance fetch fails (SDP version 9.20.0 +):

snyk container test <image> --json > out.json
jq . out.json

The container.spec.ts acceptance 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

…-1765]

`processError` combines the command exit error with collected network errors
via `errors.Join`, both in `FindMostRelevantError` and `createErrorWithExitCode`.
A joined error does not satisfy the direct type assertions in `displayError`,
so handled auxiliary errors were printed after valid command output - emitting
a second JSON object and breaking `JSON.parse(stdout)`.

Decide displayability at the top of `processError`, while the concrete error
type is still intact, and pass that decision through to teardown. The combined
error is still used for exit-code derivation and analytics, so both are
unchanged.

Also pins snyk-docker-plugin to 9.20.0 so CI exercises the failure this fixes:
9.20.0 performs a best-effort provenance fetch whose handled failure is what
triggered the duplicate output (see CLIA-1576).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@snyk-io

snyk-io Bot commented Aug 13, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️ There are multiple commits on your branch, please squash them locally before merging!
⚠️

"[fix(cli): prevent handled errors from producing duplicate output CLI-1765](#7130)" is too long. Keep the first line of your commit message under 72 characters.

⚠️

"Merge branch 'CLI-1765/prevent-duplicate-error-output' of github.com:snyk/cli into CLI-1765/prevent-duplicate-error-output" is too long. Keep the first line of your commit message under 72 characters.

⚠️

"[fix(cli): stop printing handled errors after valid command output CLI-1765](https://api.github.com/repos/snyk/cli/git/commits/cf9231fc0b9af08d76e028d942d66a8eb054d526)" is too long. Keep the first line of your commit message under 72 characters.

⚠️

"[fix(cli): prevent handled errors from producing duplicate output CLI-1765](https://api.github.com/repos/snyk/cli/git/commits/ba58193a573fa9df9c368990c7c8ab0b48790f8f)" is too long. Keep the first line of your commit message under 72 characters.

Generated by 🚫 dangerJS against 94dcdff

FindMostRelevantError returns promoted errors (e.g. maintenance windows)
uncombined, so judge those on their own merits rather than on the original
error. Without this, a maintenance notice was suppressed whenever the command
also exited non-zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bdemeo12

Copy link
Copy Markdown
Contributor Author

This PR originates from: #7047

…I-1765]

Errors are combined with errors.Join before reaching displayError, which hides
their concrete type from its direct type assertions. Handled errors therefore
slipped past the guard and were printed after the command's real output,
emitting a second JSON object and breaking JSON.parse(stdout).

displayError now inspects a combined error member by member. When the command
exited with a non-failure code it produced valid output, so anything collected
alongside it is auxiliary and stays hidden. When the command failed, errors are
suppressed only if every one of them is individually suppressible, so genuine
failures such as authentication errors still reach the user.

Pins snyk-docker-plugin to 9.20.0 so CI exercises the failure being fixed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bdemeo12
bdemeo12 force-pushed the CLI-1765/prevent-duplicate-error-output branch from 125499b to cf9231f Compare August 14, 2026 14:30
Resolved package.json / package-lock.json in favour of snyk-docker-plugin
9.20.0. main pinned 9.19.0 to avoid the duplicate-output bug; this branch is
the fix for that bug, so it keeps 9.20.0 to exercise it in CI.
…snyk/cli into CLI-1765/prevent-duplicate-error-output
Comment thread cliv2/pkg/core/main.go

for _, err := range unwrappedErrs {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && exitErr.ExitCode() < constants.SNYK_EXIT_CODE_ERROR {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An exit code below SNYK_EXIT_CODE_ERROR means the command worked and anything alongside it is noise

Comment thread cliv2/pkg/core/main.go
//
// Joined errors match no direct type assertion, so they are unwrapped and checked
// one at a time.
func shouldSuppressDisplay(err error) bool {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All errors are combined here:

resultErrorList = append([]error{resultError}, resultErrorList...)

shouldSuppressDisplay returns true when there's nothing to show:

if shouldSuppressDisplay(err) {

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.:

if isExitError || isErrorWithCode || errorHasBeenShown(err) {

Now this check suppresses the display err by returning early, before the printing

A merge reintroduced an earlier approach alongside the current one, leaving
shouldSuppressDisplay and its test declared twice, so the package did not
compile.

Removes the superseded design: processError returns to two values, tearDown
calls displayError directly, and the isCombined helper and duplicate
declarations are gone. displayError alone decides what to print.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant