Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cliv2-private/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/snyk/cli/cliv2-private

go 1.26.5
go 1.26.6

require (
github.com/snyk/ambient-canary v0.0.0-20260722064253-fba619a134a9
Expand Down
2 changes: 1 addition & 1 deletion cliv2/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/snyk/cli/cliv2

go 1.26.5
go 1.26.6

require (
github.com/elazarl/goproxy v1.7.2
Expand Down
36 changes: 33 additions & 3 deletions cliv2/pkg/core/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,11 +455,41 @@ func doctorTip(isCI bool) string {
return "Try snyk doctor: `snyk <command> -d 2>&1 | snyk doctor --stdin`"
}

// shouldSuppressDisplay reports whether err is worth printing.
//
// Joined errors match no direct type assertion, so they are unwrapped and checked
// one at a time.
func shouldSuppressDisplay(err error) bool {
if wrappedErr, ok := err.(interface{ Unwrap() []error }); ok {
unwrappedErrs := wrappedErr.Unwrap()
if len(unwrappedErrs) == 0 {
return false
}

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

for _, err := range unwrappedErrs {
if !shouldSuppressDisplay(err) {
return false
}
}
return true
}

_, isExitError := err.(*exec.ExitError)
_, isErrorWithCode := err.(*cli_errors.ErrorWithExitCode)

return isExitError || isErrorWithCode || errorHasBeenShown(err)
}

func displayError(err error, userInterface ui.UserInterface, config configuration.Configuration, ctx context.Context, isCI bool) {
if err != nil {
_, isExitError := err.(*exec.ExitError)
_, isErrorWithCode := err.(*cli_errors.ErrorWithExitCode)
if isExitError || isErrorWithCode || errorHasBeenShown(err) {
if shouldSuppressDisplay(err) {
return
}

Expand Down
44 changes: 44 additions & 0 deletions cliv2/pkg/core/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,50 @@ func Test_displayError(t *testing.T) {
})
}

func Test_shouldSuppressDisplay(t *testing.T) {
exitVulnerabilitiesFound := exec.Command("sh", "-c", "exit 1").Run()
require.Error(t, exitVulnerabilitiesFound)
exitFailure := exec.Command("sh", "-c", "exit 2").Run()
require.Error(t, exitFailure)

authError := func(alreadyShown bool) snyk_errors.Error {
return snyk_errors.Error{
Title: "Authentication error",
ErrorCode: "SNYK-0005",
Level: "error",
Meta: map[string]any{cliv2.ERROR_HAS_BEEN_DISPLAYED: alreadyShown},
}
}

testCases := []struct {
name string
err error
suppress bool
}{
{"nil", nil, false},
{"bare exit error", exitVulnerabilitiesFound, true},
{"error carrying only an exit code", &clierrors.ErrorWithExitCode{ExitCode: 1}, true},
{"error wrapping an exit error still shows its own message", &wrErr{wraps: exitVulnerabilitiesFound}, false},
{"plain error", fmt.Errorf("connection refused"), false},
{"catalog error", authError(false), false},

// The command failed: the user needs to know why.
{"failure combined with an error to report", errors.Join(exitFailure, authError(false)), false},
{"failure combined with an error already reported", errors.Join(exitFailure, authError(true)), true},

// The command produced valid output; anything collected is auxiliary and
// printing it would append a second, misleading result.
{"success combined with a collected error", errors.Join(exitVulnerabilitiesFound, fmt.Errorf("connection refused")), true},
{"success combined with an exit code carrier", errors.Join(exitVulnerabilitiesFound, &clierrors.ErrorWithExitCode{ExitCode: 2}), true},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.suppress, shouldSuppressDisplay(tc.err))
})
}
}

func Test_doctorTip(t *testing.T) {
t.Run("CI advertises the --input flag", func(t *testing.T) {
tip := doctorTip(true)
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
"semver": "^6.0.0",
"snyk-config": "^5.0.0",
"snyk-cpp-plugin": "^2.24.3",
"snyk-docker-plugin": "9.19.0",
"snyk-docker-plugin": "9.20.0",
"snyk-go-plugin": "2.2.1",
"snyk-gradle-plugin": "7.1.2",
"snyk-module": "3.1.0",
Expand Down