Skip to content

os/exec: ExitError does not conform to errors best practice via errors.Is() #51120

@johnnybubonic

Description

@johnnybubonic

What version of Go are you using (go version)?

$ go version
go version go1.17.6 linux/amd64

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/[REDACTED]/.cache/go-build"
GOENV="/home/[REDACTED]/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/[REDACTED]/go/pkg/mod"
GONOPROXY="[REDACTED],[REDACTED]/*,github.com/[REDACTED]/*"
GONOSUMDB="[REDACTED],[REDACTED]/*,github.com/[REDACTED]/*"
GOOS="linux"
GOPATH="/opt/dev/go"
GOPRIVATE="[REDACTED],[REDACTED]/*,github.com/[REDACTED]/*"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.17.6"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/[REDACTED]/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build1498898721=/tmp/go-build -gno-record-gcc-switches"

What did you do?

It is impossible to use errors.Is() with exec.ExitError as the result of a command execution. Consider the following:

import (
	"errors"
	"log"
	"os/exec"
)

var err error

func main() {

	var cmd *exec.Cmd

	cmd = exec.Command("somecommand", "somearg1")
	if err = cmd.Start(); err != nil {
		log.Panicln(err)
	}
	
	if err = cmd.Wait(); err != nil {
		if errors.Is(err, exec.ExitError) { // THIS WILL FAIL SYNTAX, as exec.ExitError.Error() expects a pointer receiver.
			_ = "" // Do something here.
		}
	}
	// ...
}

Because the structure field values that the *exec.ExitError contains are unpredictable, we cannot do a direct comparison by creating an exec.ExitError to compare against.

Because .Wait() (and .Run, etc.) may return errors other than *exec.ExitError, we can't simply assign the return as an *exec.ExitError (and indeed, the returned is an error interface).

This means in order to simply to determine if it's a runtime non-exec.ExitError error or a non-zero status on Linux (et. al.), one must use typeswitching or typecasting, use errors.As and check the bool of that return with an explicitly declared var for the .As(), or just simply use .Run() and hope that your command returns 0.

This is immensely cumbersome for something that oughtn't be, considering the incredible prevalence of os/exec.Command executions in many third-party libraries.

What did you expect to see?

N/A

What did you see instead?

N/A

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions