Remove exit error types (#352)

* remove exit error types

* reinstate error wrapping for context errs
diff --git a/tfexec/cmd_default.go b/tfexec/cmd_default.go
index 6d7b768..7323d55 100644
--- a/tfexec/cmd_default.go
+++ b/tfexec/cmd_default.go
@@ -40,11 +40,14 @@
 	}
 
 	err = cmd.Start()
-	if err == nil && ctx.Err() != nil {
-		err = ctx.Err()
+	if ctx.Err() != nil {
+		return cmdErr{
+			err:    err,
+			ctxErr: ctx.Err(),
+		}
 	}
 	if err != nil {
-		return tf.wrapExitError(ctx, err, "")
+		return err
 	}
 
 	var errStdout, errStderr error
@@ -66,19 +69,22 @@
 	wg.Wait()
 
 	err = cmd.Wait()
-	if err == nil && ctx.Err() != nil {
-		err = ctx.Err()
+	if ctx.Err() != nil {
+		return cmdErr{
+			err:    err,
+			ctxErr: ctx.Err(),
+		}
 	}
 	if err != nil {
-		return tf.wrapExitError(ctx, err, errBuf.String())
+		return err
 	}
 
 	// Return error if there was an issue reading the std out/err
 	if errStdout != nil && ctx.Err() != nil {
-		return tf.wrapExitError(ctx, errStdout, errBuf.String())
+		return errStdout
 	}
 	if errStderr != nil && ctx.Err() != nil {
-		return tf.wrapExitError(ctx, errStderr, errBuf.String())
+		return errStderr
 	}
 
 	return nil
diff --git a/tfexec/cmd_linux.go b/tfexec/cmd_linux.go
index 6fa40e0..9975791 100644
--- a/tfexec/cmd_linux.go
+++ b/tfexec/cmd_linux.go
@@ -45,11 +45,14 @@
 	}
 
 	err = cmd.Start()
-	if err == nil && ctx.Err() != nil {
-		err = ctx.Err()
+	if ctx.Err() != nil {
+		return cmdErr{
+			err:    err,
+			ctxErr: ctx.Err(),
+		}
 	}
 	if err != nil {
-		return tf.wrapExitError(ctx, err, "")
+		return err
 	}
 
 	var errStdout, errStderr error
@@ -71,19 +74,22 @@
 	wg.Wait()
 
 	err = cmd.Wait()
-	if err == nil && ctx.Err() != nil {
-		err = ctx.Err()
+	if ctx.Err() != nil {
+		return cmdErr{
+			err:    err,
+			ctxErr: ctx.Err(),
+		}
 	}
 	if err != nil {
-		return tf.wrapExitError(ctx, err, errBuf.String())
+		return err
 	}
 
 	// Return error if there was an issue reading the std out/err
 	if errStdout != nil && ctx.Err() != nil {
-		return tf.wrapExitError(ctx, errStdout, errBuf.String())
+		return errStdout
 	}
 	if errStderr != nil && ctx.Err() != nil {
-		return tf.wrapExitError(ctx, errStderr, errBuf.String())
+		return errStderr
 	}
 
 	return nil
diff --git a/tfexec/errors.go b/tfexec/errors.go
index 7a32ef2..3bbb431 100644
--- a/tfexec/errors.go
+++ b/tfexec/errors.go
@@ -1,6 +1,9 @@
 package tfexec
 
-import "fmt"
+import (
+	"context"
+	"fmt"
+)
 
 // this file contains non-parsed exported errors
 
@@ -37,3 +40,25 @@
 func (err *ErrManualEnvVar) Error() string {
 	return fmt.Sprintf("manual setting of env var %q detected", err.Name)
 }
+
+// cmdErr is a custom error type to be returned when a cmd exits with a context
+// error such as context.Canceled or context.DeadlineExceeded.
+// The type is specifically designed to respond true to errors.Is for these two
+// errors.
+// See https://github.com/golang/go/issues/21880 for why this is necessary.
+type cmdErr struct {
+	err    error
+	ctxErr error
+}
+
+func (e cmdErr) Is(target error) bool {
+	switch target {
+	case context.DeadlineExceeded, context.Canceled:
+		return e.ctxErr == context.DeadlineExceeded || e.ctxErr == context.Canceled
+	}
+	return false
+}
+
+func (e cmdErr) Error() string {
+	return e.err.Error()
+}
diff --git a/tfexec/exit_errors.go b/tfexec/exit_errors.go
deleted file mode 100644
index cd84bf5..0000000
--- a/tfexec/exit_errors.go
+++ /dev/null
@@ -1,348 +0,0 @@
-package tfexec
-
-import (
-	"context"
-	"fmt"
-	"os/exec"
-	"regexp"
-	"strings"
-	"text/template"
-)
-
-// this file contains errors parsed from stderr
-
-var (
-	// The "Required variable not set:" case is for 0.11
-	missingVarErrRegexp  = regexp.MustCompile(`Error: No value for required variable|Error: Required variable not set:`)
-	missingVarNameRegexp = regexp.MustCompile(`The root module input variable\s"(.+)"\sis\snot\sset,\sand\shas\sno\sdefault|Error: Required variable not set: (.+)`)
-
-	usageRegexp = regexp.MustCompile(`Too many command line arguments|^Usage: .*Options:.*|Error: Invalid -\d+ option`)
-
-	noInitErrRegexp = regexp.MustCompile(
-		// UNINITIALISED PROVIDERS/MODULES
-		`Error: Could not satisfy plugin requirements|` +
-			`Error: Could not load plugin|` + // v0.13
-			`Please run \"terraform init\"|` + // v1.1.0 early alpha versions (ref 89b05050)
-			`run:\s+terraform init|` + // v1.1.0 (ref df578afd)
-			`Run\s+\"terraform init\"|` + // v1.2.0
-
-			// UNINITIALISED BACKENDS
-			`Error: Initialization required.|` + // v0.13
-			`Error: Backend initialization required, please run \"terraform init\"`, // v0.15
-	)
-
-	noConfigErrRegexp = regexp.MustCompile(`Error: No configuration files`)
-
-	workspaceDoesNotExistRegexp = regexp.MustCompile(`Workspace "(.+)" doesn't exist.`)
-
-	workspaceAlreadyExistsRegexp = regexp.MustCompile(`Workspace "(.+)" already exists`)
-
-	tfVersionMismatchErrRegexp        = regexp.MustCompile(`Error: The currently running version of Terraform doesn't meet the|Error: Unsupported Terraform Core version`)
-	tfVersionMismatchConstraintRegexp = regexp.MustCompile(`required_version = "(.+)"|Required version: (.+)\b`)
-	configInvalidErrRegexp            = regexp.MustCompile(
-		`There are some problems with the configuration, described below.|` +
-			`Error: Unsupported block type|Error: Unsupported argument`, // v1.4+
-	)
-
-	stateLockErrRegexp     = regexp.MustCompile(`Error acquiring the state lock`)
-	stateLockInfoRegexp    = regexp.MustCompile(`Lock Info:\n\s*ID:\s*([^\n]+)\n\s*Path:\s*([^\n]+)\n\s*Operation:\s*([^\n]+)\n\s*Who:\s*([^\n]+)\n\s*Version:\s*([^\n]+)\n\s*Created:\s*([^\n]+)\n`)
-	statePlanReadErrRegexp = regexp.MustCompile(
-		`Terraform couldn't read the given file as a state or plan file.|` +
-			`Error: Failed to read the given file as a state or plan file`)
-	lockIdInvalidErrRegexp = regexp.MustCompile(`Failed to unlock state: `)
-)
-
-func (tf *Terraform) wrapExitError(ctx context.Context, err error, stderr string) error {
-	exitErr, ok := err.(*exec.ExitError)
-	if !ok {
-		// not an exit error, short circuit, nothing to wrap
-		return err
-	}
-
-	ctxErr := ctx.Err()
-
-	// nothing to parse, return early
-	errString := strings.TrimSpace(stderr)
-	if errString == "" {
-		return &unwrapper{exitErr, ctxErr}
-	}
-
-	switch {
-	case tfVersionMismatchErrRegexp.MatchString(stderr):
-		constraint := ""
-		constraints := tfVersionMismatchConstraintRegexp.FindStringSubmatch(stderr)
-		for i := 1; i < len(constraints); i++ {
-			constraint = strings.TrimSpace(constraints[i])
-			if constraint != "" {
-				break
-			}
-		}
-
-		if constraint == "" {
-			// hardcode a value here for weird cases (incl. 0.12)
-			constraint = "unknown"
-		}
-
-		// only set this if it happened to be cached already
-		ver := ""
-		if tf != nil && tf.execVersion != nil {
-			ver = tf.execVersion.String()
-		}
-
-		return &ErrTFVersionMismatch{
-			unwrapper: unwrapper{exitErr, ctxErr},
-
-			Constraint: constraint,
-			TFVersion:  ver,
-		}
-	case missingVarErrRegexp.MatchString(stderr):
-		name := ""
-		names := missingVarNameRegexp.FindStringSubmatch(stderr)
-		for i := 1; i < len(names); i++ {
-			name = strings.TrimSpace(names[i])
-			if name != "" {
-				break
-			}
-		}
-
-		return &ErrMissingVar{
-			unwrapper: unwrapper{exitErr, ctxErr},
-
-			VariableName: name,
-		}
-	case usageRegexp.MatchString(stderr):
-		return &ErrCLIUsage{
-			unwrapper: unwrapper{exitErr, ctxErr},
-
-			stderr: stderr,
-		}
-	case noInitErrRegexp.MatchString(stderr):
-		return &ErrNoInit{
-			unwrapper: unwrapper{exitErr, ctxErr},
-
-			stderr: stderr,
-		}
-	case noConfigErrRegexp.MatchString(stderr):
-		return &ErrNoConfig{
-			unwrapper: unwrapper{exitErr, ctxErr},
-
-			stderr: stderr,
-		}
-	case workspaceDoesNotExistRegexp.MatchString(stderr):
-		submatches := workspaceDoesNotExistRegexp.FindStringSubmatch(stderr)
-		if len(submatches) == 2 {
-			return &ErrNoWorkspace{
-				unwrapper: unwrapper{exitErr, ctxErr},
-
-				Name: submatches[1],
-			}
-		}
-	case workspaceAlreadyExistsRegexp.MatchString(stderr):
-		submatches := workspaceAlreadyExistsRegexp.FindStringSubmatch(stderr)
-		if len(submatches) == 2 {
-			return &ErrWorkspaceExists{
-				unwrapper: unwrapper{exitErr, ctxErr},
-
-				Name: submatches[1],
-			}
-		}
-	case configInvalidErrRegexp.MatchString(stderr):
-		return &ErrConfigInvalid{stderr: stderr}
-	case stateLockErrRegexp.MatchString(stderr):
-		submatches := stateLockInfoRegexp.FindStringSubmatch(stderr)
-		if len(submatches) == 7 {
-			return &ErrStateLocked{
-				unwrapper: unwrapper{exitErr, ctxErr},
-
-				ID:        submatches[1],
-				Path:      submatches[2],
-				Operation: submatches[3],
-				Who:       submatches[4],
-				Version:   submatches[5],
-				Created:   submatches[6],
-			}
-		}
-	case statePlanReadErrRegexp.MatchString(stderr):
-		return &ErrStatePlanRead{stderr: stderr}
-	case lockIdInvalidErrRegexp.MatchString(stderr):
-		return &ErrLockIdInvalid{stderr: stderr}
-	}
-
-	return fmt.Errorf("%w\n%s", &unwrapper{exitErr, ctxErr}, stderr)
-}
-
-type unwrapper struct {
-	err    error
-	ctxErr error
-}
-
-func (u *unwrapper) Unwrap() error {
-	return u.err
-}
-
-func (u *unwrapper) Is(target error) bool {
-	switch target {
-	case context.DeadlineExceeded, context.Canceled:
-		return u.ctxErr == context.DeadlineExceeded ||
-			u.ctxErr == context.Canceled
-	}
-	return false
-}
-
-func (u *unwrapper) Error() string {
-	return u.err.Error()
-}
-
-type ErrConfigInvalid struct {
-	stderr string
-}
-
-func (e *ErrConfigInvalid) Error() string {
-	return "configuration is invalid"
-}
-
-type ErrMissingVar struct {
-	unwrapper
-
-	VariableName string
-}
-
-func (err *ErrMissingVar) Error() string {
-	return fmt.Sprintf("variable %q was required but not supplied", err.VariableName)
-}
-
-type ErrNoWorkspace struct {
-	unwrapper
-
-	Name string
-}
-
-func (err *ErrNoWorkspace) Error() string {
-	return fmt.Sprintf("workspace %q does not exist", err.Name)
-}
-
-// ErrWorkspaceExists is returned when creating a workspace that already exists
-type ErrWorkspaceExists struct {
-	unwrapper
-
-	Name string
-}
-
-func (err *ErrWorkspaceExists) Error() string {
-	return fmt.Sprintf("workspace %q already exists", err.Name)
-}
-
-type ErrNoInit struct {
-	unwrapper
-
-	stderr string
-}
-
-func (e *ErrNoInit) Error() string {
-	return e.stderr
-}
-
-type ErrStatePlanRead struct {
-	unwrapper
-
-	stderr string
-}
-
-func (e *ErrStatePlanRead) Error() string {
-	return e.stderr
-}
-
-type ErrNoConfig struct {
-	unwrapper
-
-	stderr string
-}
-
-func (e *ErrNoConfig) Error() string {
-	return e.stderr
-}
-
-type ErrLockIdInvalid struct {
-	unwrapper
-
-	stderr string
-}
-
-func (e *ErrLockIdInvalid) Error() string {
-	return e.stderr
-}
-
-// ErrCLIUsage is returned when the combination of flags or arguments is incorrect.
-//
-//	CLI indicates usage errors in three different ways: either
-//
-// 1. Exit 1, with a custom error message on stderr.
-// 2. Exit 1, with command usage logged to stderr.
-// 3. Exit 127, with command usage logged to stdout.
-// Currently cases 1 and 2 are handled.
-// TODO KEM: Handle exit 127 case. How does this work on non-Unix platforms?
-type ErrCLIUsage struct {
-	unwrapper
-
-	stderr string
-}
-
-func (e *ErrCLIUsage) Error() string {
-	return e.stderr
-}
-
-// ErrTFVersionMismatch is returned when the running Terraform version is not compatible with the
-// value specified for required_version in the terraform block.
-type ErrTFVersionMismatch struct {
-	unwrapper
-
-	TFVersion string
-
-	// Constraint is not returned in the error messaging on 0.12
-	Constraint string
-}
-
-func (e *ErrTFVersionMismatch) Error() string {
-	version := "version"
-	if e.TFVersion != "" {
-		version = e.TFVersion
-	}
-
-	requirement := ""
-	if e.Constraint != "" {
-		requirement = fmt.Sprintf(" (%s required)", e.Constraint)
-	}
-
-	return fmt.Sprintf("terraform %s not supported by configuration%s",
-		version, requirement)
-}
-
-// ErrStateLocked is returned when the state lock is already held by another process.
-type ErrStateLocked struct {
-	unwrapper
-
-	ID        string
-	Path      string
-	Operation string
-	Who       string
-	Version   string
-	Created   string
-}
-
-func (e *ErrStateLocked) Error() string {
-	tmpl := `Lock Info:
-  ID:        {{.ID}}
-  Path:      {{.Path}}
-  Operation: {{.Operation}}
-  Who:       {{.Who}}
-  Version:   {{.Version}}
-  Created:   {{.Created}}
-`
-
-	t := template.Must(template.New("LockInfo").Parse(tmpl))
-	var out strings.Builder
-	if err := t.Execute(&out, e); err != nil {
-		return "error acquiring the state lock"
-	}
-	return fmt.Sprintf("error acquiring the state lock: %v", out.String())
-}
diff --git a/tfexec/internal/e2etest/errors_test.go b/tfexec/internal/e2etest/errors_test.go
index 9577555..d75986d 100644
--- a/tfexec/internal/e2etest/errors_test.go
+++ b/tfexec/internal/e2etest/errors_test.go
@@ -54,32 +54,15 @@
 		shortVarName := "no_default"
 		longVarName := "no_default_really_long_variable_name_that_will_line_wrap_tf_output"
 
-		// Test for ErrMissingVar and properly formatted error message on shorter variable names
 		_, err = tf.Plan(context.Background(), tfexec.Var(longVarName+"=foo"))
 		if err == nil {
 			t.Fatalf("expected error running Plan, none returned")
 		}
-		var e *tfexec.ErrMissingVar
-		if !errors.As(err, &e) {
-			t.Fatalf("expected ErrMissingVar, got %T, %s", err, err)
-		}
 
-		if e.VariableName != shortVarName {
-			t.Fatalf("expected missing %s, got %q", shortVarName, e.VariableName)
-		}
-
-		// Test for ErrMissingVar and properly formatted error message on long variable names
 		_, err = tf.Plan(context.Background(), tfexec.Var(shortVarName+"=foo"))
 		if err == nil {
 			t.Fatalf("expected error running Plan, none returned")
 		}
-		if !errors.As(err, &e) {
-			t.Fatalf("expected ErrMissingVar, got %T, %s", err, err)
-		}
-
-		if e.VariableName != longVarName {
-			t.Fatalf("expected missing %s, got %q", longVarName, e.VariableName)
-		}
 
 		var ee *exec.ExitError
 		if !errors.As(err, &ee) {
@@ -107,20 +90,6 @@
 			t.Fatal("expected error, but didn't find one")
 		}
 
-		var e *tfexec.ErrTFVersionMismatch
-		if !errors.As(err, &e) {
-			t.Fatalf("expected ErrTFVersionMismatch, got %T, %s", err, err)
-		}
-
-		// in 0.12, we just return "unknown" as the specifics are not included in the error messaging
-		if e.Constraint != "unknown" && e.Constraint != ">99.0.0" {
-			t.Fatalf("unexpected constraint %q", e.Constraint)
-		}
-
-		if e.TFVersion != tfv.String() {
-			t.Fatalf("expected %q, got %q", tfv.String(), e.TFVersion)
-		}
-
 		var ee *exec.ExitError
 		if !errors.As(err, &ee) {
 			t.Fatalf("expected exec.ExitError, got %T, %s", err, err)
@@ -139,11 +108,6 @@
 		if err == nil {
 			t.Fatal("expected error, but didn't find one")
 		}
-
-		var stateLockedErr *tfexec.ErrStateLocked
-		if !errors.As(err, &stateLockedErr) {
-			t.Fatalf("expected ErrTFVersionMismatch, got %T, %s", err, err)
-		}
 	})
 }
 
diff --git a/tfexec/internal/e2etest/force_unlock_test.go b/tfexec/internal/e2etest/force_unlock_test.go
index c5d626a..1280aa3 100644
--- a/tfexec/internal/e2etest/force_unlock_test.go
+++ b/tfexec/internal/e2etest/force_unlock_test.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"errors"
 	"testing"
 
 	"github.com/hashicorp/go-version"
@@ -51,9 +50,5 @@
 		if err == nil {
 			t.Fatalf("expected error when running ForceUnlock with invalid lock id")
 		}
-		var foErr *tfexec.ErrLockIdInvalid
-		if !errors.As(err, &foErr) {
-			t.Fatalf("expected ErrLockIdInvalid, %T returned: %s", err, err)
-		}
 	})
 }
diff --git a/tfexec/internal/e2etest/show_test.go b/tfexec/internal/e2etest/show_test.go
index e1cb92b..72e0ae9 100644
--- a/tfexec/internal/e2etest/show_test.go
+++ b/tfexec/internal/e2etest/show_test.go
@@ -111,16 +111,15 @@
 }
 
 func TestShow_noInitBasic(t *testing.T) {
-	// Prior to v1.2.0, running show before init always results in ErrNoInit.
+	// Prior to v1.2.0, running show before init always results in an error.
 	// In the basic case, in which the local backend is implicit and there are
 	// no providers to download, this is unintended behaviour, as
 	// init is not actually necessary. This is considered a known issue in
 	// pre-1.2.0 versions.
 	runTestWithVersions(t, []string{testutil.Latest012, testutil.Latest013, testutil.Latest014, testutil.Latest015, testutil.Latest_v1, testutil.Latest_v1_1}, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
-		var noInit *tfexec.ErrNoInit
 		_, err := tf.Show(context.Background())
-		if !errors.As(err, &noInit) {
-			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 
@@ -149,16 +148,15 @@
 }
 
 func TestShow_noInitModule(t *testing.T) {
-	// Prior to v1.2.0, running show before init always results in ErrNoInit.
+	// Prior to v1.2.0, running show before init always results in an error.
 	// In the basic case, in which the local backend is implicit and there are
 	// no providers to download, this is unintended behaviour, as
 	// init is not actually necessary. This is considered a known issue in
 	// pre-1.2.0 versions.
 	runTestWithVersions(t, []string{testutil.Latest012, testutil.Latest013, testutil.Latest014, testutil.Latest015, testutil.Latest_v1, testutil.Latest_v1_1}, "registry_module", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
-		var noInit *tfexec.ErrNoInit
 		_, err := tf.Show(context.Background())
-		if !errors.As(err, &noInit) {
-			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 
@@ -191,10 +189,9 @@
 			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
 		}
 
-		var noInit *tfexec.ErrNoInit
 		_, err := tf.Show(context.Background())
-		if !errors.As(err, &noInit) {
-			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 }
@@ -205,10 +202,9 @@
 			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
 		}
 
-		var noInit *tfexec.ErrNoInit
 		_, err := tf.Show(context.Background())
-		if !errors.As(err, &noInit) {
-			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 }
@@ -219,10 +215,9 @@
 			t.Skip("cloud backend was added in Terraform 1.1, so test is not valid")
 		}
 
-		var noInit *tfexec.ErrNoInit
 		_, err := tf.Show(context.Background())
-		if !errors.As(err, &noInit) {
-			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 }
@@ -237,10 +232,9 @@
 			t.Skip("etcd backend was removed in Terraform 1.3, so test is not valid")
 		}
 
-		var noInit *tfexec.ErrNoInit
 		_, err := tf.Show(context.Background())
-		if !errors.As(err, &noInit) {
-			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 }
@@ -251,10 +245,9 @@
 			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
 		}
 
-		var noInit *tfexec.ErrNoInit
 		_, err := tf.Show(context.Background())
-		if !errors.As(err, &noInit) {
-			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 }
@@ -270,10 +263,9 @@
 			t.Fatalf("error running Init in test directory: %s", err)
 		}
 
-		var statePlanReadErr *tfexec.ErrStatePlanRead
 		_, err = tf.ShowStateFile(context.Background(), "statefilefoo")
-		if !errors.As(err, &statePlanReadErr) {
-			t.Fatalf("expected error ErrStatePlanRead, got %T: %s", err, err)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 }
diff --git a/tfexec/internal/e2etest/validate_test.go b/tfexec/internal/e2etest/validate_test.go
index d57ebe2..e2f247c 100644
--- a/tfexec/internal/e2etest/validate_test.go
+++ b/tfexec/internal/e2etest/validate_test.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"errors"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -46,12 +45,10 @@
 		if err != nil {
 			t.Logf("error initializing: %s", err)
 
-			// allow for invalid config errors only here
-			// 0.13 will return this, 0.12 will not
+			// 0.13 will error, 0.12 will not
 			// unsure why 0.12 terraform init does not have a non-zero exit code for syntax problems
-			var confErr *tfexec.ErrConfigInvalid
-			if !errors.As(err, &confErr) {
-				t.Fatalf("expected err ErrConfigInvalid, got %T: %s", err, err)
+			if err == nil {
+				t.Fatalf("expected error, but did not get one")
 			}
 		}
 
diff --git a/tfexec/internal/e2etest/workspace_test.go b/tfexec/internal/e2etest/workspace_test.go
index 35964ec..6848965 100644
--- a/tfexec/internal/e2etest/workspace_test.go
+++ b/tfexec/internal/e2etest/workspace_test.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"errors"
 	"reflect"
 	"testing"
 
@@ -45,12 +44,8 @@
 	runTest(t, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
 		const doesNotExistWorkspace = "does-not-exist"
 		err := tf.WorkspaceSelect(context.Background(), doesNotExistWorkspace)
-		var wsErr *tfexec.ErrNoWorkspace
-		if !errors.As(err, &wsErr) {
-			t.Fatalf("expected ErrNoWorkspace, %T returned: %s", err, err)
-		}
-		if wsErr.Name != doesNotExistWorkspace {
-			t.Fatalf("expected %q, got %q", doesNotExistWorkspace, wsErr.Name)
+		if err == nil {
+			t.Fatalf("expected error, but did not get one")
 		}
 	})
 }
@@ -71,12 +66,8 @@
 		t.Run("create existing workspace", func(t *testing.T) {
 			err := tf.WorkspaceNew(context.Background(), newWorkspace)
 
-			var wsErr *tfexec.ErrWorkspaceExists
-			if !errors.As(err, &wsErr) {
-				t.Fatalf("expected ErrWorkspaceExists, %T returned: %s", err, err)
-			}
-			if wsErr.Name != newWorkspace {
-				t.Fatalf("expected %q, got %q", newWorkspace, wsErr.Name)
+			if err == nil {
+				t.Fatalf("expected error, but did not get one")
 			}
 		})
 	})