tfexec: Add provider reattach support to all `terraform workspace` subcommands (#556)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebd7d72..ac93c8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# 0.25.0 (Unreleased)
+
+ENHANCEMENTS:
+- tfexec: Added provider reattach support to all `terraform workspace` subcommands ([#556](https://github.com/hashicorp/terraform-exec/pull/556))
+
 # 0.24.0 (September 17, 2025)
 
 ENHANCEMENTS:
diff --git a/tfexec/workspace_delete.go b/tfexec/workspace_delete.go
index f2a17e6..30797fc 100644
--- a/tfexec/workspace_delete.go
+++ b/tfexec/workspace_delete.go
@@ -11,9 +11,10 @@
 )
 
 type workspaceDeleteConfig struct {
-	lock        bool
-	lockTimeout string
-	force       bool
+	lock         bool
+	lockTimeout  string
+	force        bool
+	reattachInfo ReattachInfo
 }
 
 var defaultWorkspaceDeleteOptions = workspaceDeleteConfig{
@@ -38,6 +39,10 @@
 	conf.force = opt.force
 }
 
+func (opt *ReattachOption) configureWorkspaceDelete(conf *workspaceDeleteConfig) {
+	conf.reattachInfo = opt.info
+}
+
 // WorkspaceDelete represents the workspace delete subcommand to the Terraform CLI.
 func (tf *Terraform) WorkspaceDelete(ctx context.Context, workspace string, opts ...WorkspaceDeleteCmdOption) error {
 	cmd, err := tf.workspaceDeleteCmd(ctx, workspace, opts...)
@@ -78,7 +83,16 @@
 
 	args = append(args, workspace)
 
-	cmd := tf.buildTerraformCmd(ctx, nil, args...)
+	mergeEnv := map[string]string{}
+	if c.reattachInfo != nil {
+		reattachStr, err := c.reattachInfo.marshalString()
+		if err != nil {
+			return nil, err
+		}
+		mergeEnv[reattachEnvVar] = reattachStr
+	}
+
+	cmd := tf.buildTerraformCmd(ctx, mergeEnv, args...)
 
 	return cmd, nil
 }
diff --git a/tfexec/workspace_delete_test.go b/tfexec/workspace_delete_test.go
index 41cd52a..bfc00e5 100644
--- a/tfexec/workspace_delete_test.go
+++ b/tfexec/workspace_delete_test.go
@@ -50,4 +50,30 @@
 			"workspace-name",
 		}, nil, workspaceDeleteCmd)
 	})
+
+	t.Run("reattach config", func(t *testing.T) {
+		workspaceDeleteCmd, err := tf.workspaceDeleteCmd(context.Background(), "workspace-name", Reattach(map[string]ReattachConfig{
+			"registry.terraform.io/hashicorp/examplecloud": {
+				Protocol:        "grpc",
+				ProtocolVersion: 6,
+				Pid:             1234,
+				Test:            true,
+				Addr: ReattachConfigAddr{
+					Network: "unix",
+					String:  "/fake_folder/T/plugin123",
+				},
+			},
+		}))
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assertCmd(t, []string{
+			"workspace", "delete",
+			"-no-color",
+			"workspace-name",
+		}, map[string]string{
+			"TF_REATTACH_PROVIDERS": `{"registry.terraform.io/hashicorp/examplecloud":{"Protocol":"grpc","ProtocolVersion":6,"Pid":1234,"Test":true,"Addr":{"Network":"unix","String":"/fake_folder/T/plugin123"}}}`,
+		}, workspaceDeleteCmd)
+	})
 }
diff --git a/tfexec/workspace_list.go b/tfexec/workspace_list.go
index 1b4bec3..b451731 100644
--- a/tfexec/workspace_list.go
+++ b/tfexec/workspace_list.go
@@ -5,18 +5,35 @@
 
 import (
 	"context"
+	"os/exec"
 	"strings"
 )
 
+type workspaceListConfig struct {
+	reattachInfo ReattachInfo
+}
+
+var defaultWorkspaceListOptions = workspaceListConfig{}
+
+type WorkspaceListOption interface {
+	configureWorkspaceList(*workspaceListConfig)
+}
+
+func (opt *ReattachOption) configureWorkspaceList(conf *workspaceListConfig) {
+	conf.reattachInfo = opt.info
+}
+
 // WorkspaceList represents the workspace list subcommand to the Terraform CLI.
-func (tf *Terraform) WorkspaceList(ctx context.Context) ([]string, string, error) {
-	// TODO: [DIR] param option
-	wlCmd := tf.buildTerraformCmd(ctx, nil, "workspace", "list", "-no-color")
+func (tf *Terraform) WorkspaceList(ctx context.Context, opts ...WorkspaceListOption) ([]string, string, error) {
+	wlCmd, err := tf.workspaceListCmd(ctx, opts...)
+	if err != nil {
+		return nil, "", err
+	}
 
 	var outBuf strings.Builder
 	wlCmd.Stdout = &outBuf
 
-	err := tf.runTerraformCmd(ctx, wlCmd)
+	err = tf.runTerraformCmd(ctx, wlCmd)
 	if err != nil {
 		return nil, "", err
 	}
@@ -28,6 +45,25 @@
 
 const currentWorkspacePrefix = "* "
 
+func (tf *Terraform) workspaceListCmd(ctx context.Context, opts ...WorkspaceListOption) (*exec.Cmd, error) {
+	c := defaultWorkspaceListOptions
+
+	for _, o := range opts {
+		o.configureWorkspaceList(&c)
+	}
+
+	mergeEnv := map[string]string{}
+	if c.reattachInfo != nil {
+		reattachStr, err := c.reattachInfo.marshalString()
+		if err != nil {
+			return nil, err
+		}
+		mergeEnv[reattachEnvVar] = reattachStr
+	}
+
+	return tf.buildTerraformCmd(ctx, mergeEnv, "workspace", "list", "-no-color"), nil
+}
+
 func parseWorkspaceList(stdout string) ([]string, string) {
 	lines := strings.Split(stdout, "\n")
 
diff --git a/tfexec/workspace_list_test.go b/tfexec/workspace_list_test.go
index 60bb67e..72d305e 100644
--- a/tfexec/workspace_list_test.go
+++ b/tfexec/workspace_list_test.go
@@ -4,11 +4,61 @@
 package tfexec
 
 import (
+	"context"
 	"fmt"
 	"reflect"
 	"testing"
+
+	"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
 )
 
+func TestWorkspaceListCmd(t *testing.T) {
+	tf, err := NewTerraform(t.TempDir(), tfVersion(t, testutil.Latest_v1))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// empty env, to avoid environ mismatch in testing
+	tf.SetEnv(map[string]string{})
+
+	t.Run("defaults", func(t *testing.T) {
+		workspaceListCmd, err := tf.workspaceListCmd(context.Background())
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assertCmd(t, []string{
+			"workspace", "list",
+			"-no-color",
+		}, nil, workspaceListCmd)
+	})
+
+	t.Run("reattach config", func(t *testing.T) {
+		workspaceListCmd, err := tf.workspaceListCmd(context.Background(), Reattach(map[string]ReattachConfig{
+			"registry.terraform.io/hashicorp/examplecloud": {
+				Protocol:        "grpc",
+				ProtocolVersion: 6,
+				Pid:             1234,
+				Test:            true,
+				Addr: ReattachConfigAddr{
+					Network: "unix",
+					String:  "/fake_folder/T/plugin123",
+				},
+			},
+		}))
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assertCmd(t, []string{
+			"workspace", "list",
+			"-no-color",
+		}, map[string]string{
+			"TF_REATTACH_PROVIDERS": `{"registry.terraform.io/hashicorp/examplecloud":{"Protocol":"grpc","ProtocolVersion":6,"Pid":1234,"Test":true,"Addr":{"Network":"unix","String":"/fake_folder/T/plugin123"}}}`,
+		}, workspaceListCmd)
+	})
+}
+
 func TestParseWorkspaceList(t *testing.T) {
 	for i, c := range []struct {
 		expected        []string
diff --git a/tfexec/workspace_new.go b/tfexec/workspace_new.go
index 921a118..e1871ec 100644
--- a/tfexec/workspace_new.go
+++ b/tfexec/workspace_new.go
@@ -11,9 +11,10 @@
 )
 
 type workspaceNewConfig struct {
-	lock        bool
-	lockTimeout string
-	copyState   string
+	lock         bool
+	lockTimeout  string
+	copyState    string
+	reattachInfo ReattachInfo
 }
 
 var defaultWorkspaceNewOptions = workspaceNewConfig{
@@ -38,6 +39,10 @@
 	conf.copyState = opt.path
 }
 
+func (opt *ReattachOption) configureWorkspaceNew(conf *workspaceNewConfig) {
+	conf.reattachInfo = opt.info
+}
+
 // WorkspaceNew represents the workspace new subcommand to the Terraform CLI.
 func (tf *Terraform) WorkspaceNew(ctx context.Context, workspace string, opts ...WorkspaceNewCmdOption) error {
 	cmd, err := tf.workspaceNewCmd(ctx, workspace, opts...)
@@ -48,8 +53,6 @@
 }
 
 func (tf *Terraform) workspaceNewCmd(ctx context.Context, workspace string, opts ...WorkspaceNewCmdOption) (*exec.Cmd, error) {
-	// TODO: [DIR] param option
-
 	c := defaultWorkspaceNewOptions
 
 	for _, o := range opts {
@@ -80,7 +83,16 @@
 
 	args = append(args, workspace)
 
-	cmd := tf.buildTerraformCmd(ctx, nil, args...)
+	mergeEnv := map[string]string{}
+	if c.reattachInfo != nil {
+		reattachStr, err := c.reattachInfo.marshalString()
+		if err != nil {
+			return nil, err
+		}
+		mergeEnv[reattachEnvVar] = reattachStr
+	}
+
+	cmd := tf.buildTerraformCmd(ctx, mergeEnv, args...)
 
 	return cmd, nil
 }
diff --git a/tfexec/workspace_new_test.go b/tfexec/workspace_new_test.go
index 744c8f6..08c4c02 100644
--- a/tfexec/workspace_new_test.go
+++ b/tfexec/workspace_new_test.go
@@ -56,4 +56,30 @@
 			"workspace-name",
 		}, nil, workspaceNewCmd)
 	})
+
+	t.Run("reattach config", func(t *testing.T) {
+		workspaceNewCmd, err := tf.workspaceNewCmd(context.Background(), "workspace-name", Reattach(map[string]ReattachConfig{
+			"registry.terraform.io/hashicorp/examplecloud": {
+				Protocol:        "grpc",
+				ProtocolVersion: 6,
+				Pid:             1234,
+				Test:            true,
+				Addr: ReattachConfigAddr{
+					Network: "unix",
+					String:  "/fake_folder/T/plugin123",
+				},
+			},
+		}))
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assertCmd(t, []string{
+			"workspace", "new",
+			"-no-color",
+			"workspace-name",
+		}, map[string]string{
+			"TF_REATTACH_PROVIDERS": `{"registry.terraform.io/hashicorp/examplecloud":{"Protocol":"grpc","ProtocolVersion":6,"Pid":1234,"Test":true,"Addr":{"Network":"unix","String":"/fake_folder/T/plugin123"}}}`,
+		}, workspaceNewCmd)
+	})
 }
diff --git a/tfexec/workspace_select.go b/tfexec/workspace_select.go
index da88472..c69ff4f 100644
--- a/tfexec/workspace_select.go
+++ b/tfexec/workspace_select.go
@@ -3,11 +3,50 @@
 
 package tfexec
 
-import "context"
+import (
+	"context"
+	"os/exec"
+)
+
+type workspaceSelectConfig struct {
+	reattachInfo ReattachInfo
+}
+
+var defaultWorkspaceSelectOptions = workspaceSelectConfig{}
+
+type WorkspaceSelectOption interface {
+	configureWorkspaceSelect(*workspaceSelectConfig)
+}
+
+func (opt *ReattachOption) configureWorkspaceSelect(conf *workspaceSelectConfig) {
+	conf.reattachInfo = opt.info
+}
 
 // WorkspaceSelect represents the workspace select subcommand to the Terraform CLI.
-func (tf *Terraform) WorkspaceSelect(ctx context.Context, workspace string) error {
-	// TODO: [DIR] param option
+func (tf *Terraform) WorkspaceSelect(ctx context.Context, workspace string, opts ...WorkspaceSelectOption) error {
+	cmd, err := tf.workspaceSelectCmd(ctx, workspace, opts...)
+	if err != nil {
+		return err
+	}
 
-	return tf.runTerraformCmd(ctx, tf.buildTerraformCmd(ctx, nil, "workspace", "select", "-no-color", workspace))
+	return tf.runTerraformCmd(ctx, cmd)
+}
+
+func (tf *Terraform) workspaceSelectCmd(ctx context.Context, workspace string, opts ...WorkspaceSelectOption) (*exec.Cmd, error) {
+	c := defaultWorkspaceSelectOptions
+
+	for _, o := range opts {
+		o.configureWorkspaceSelect(&c)
+	}
+
+	mergeEnv := map[string]string{}
+	if c.reattachInfo != nil {
+		reattachStr, err := c.reattachInfo.marshalString()
+		if err != nil {
+			return nil, err
+		}
+		mergeEnv[reattachEnvVar] = reattachStr
+	}
+
+	return tf.buildTerraformCmd(ctx, mergeEnv, "workspace", "select", "-no-color", workspace), nil
 }
diff --git a/tfexec/workspace_select_test.go b/tfexec/workspace_select_test.go
new file mode 100644
index 0000000..6d06c2c
--- /dev/null
+++ b/tfexec/workspace_select_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package tfexec
+
+import (
+	"context"
+	"testing"
+
+	"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
+)
+
+func TestWorkspaceSelectCmd(t *testing.T) {
+	tf, err := NewTerraform(t.TempDir(), tfVersion(t, testutil.Latest_v1))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// empty env, to avoid environ mismatch in testing
+	tf.SetEnv(map[string]string{})
+
+	t.Run("defaults", func(t *testing.T) {
+		workspaceSelectCmd, err := tf.workspaceSelectCmd(context.Background(), "workspace-name")
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assertCmd(t, []string{
+			"workspace", "select",
+			"-no-color",
+			"workspace-name",
+		}, nil, workspaceSelectCmd)
+	})
+
+	t.Run("reattach config", func(t *testing.T) {
+		workspaceSelectCmd, err := tf.workspaceSelectCmd(context.Background(), "workspace-name", Reattach(map[string]ReattachConfig{
+			"registry.terraform.io/hashicorp/examplecloud": {
+				Protocol:        "grpc",
+				ProtocolVersion: 6,
+				Pid:             1234,
+				Test:            true,
+				Addr: ReattachConfigAddr{
+					Network: "unix",
+					String:  "/fake_folder/T/plugin123",
+				},
+			},
+		}))
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assertCmd(t, []string{
+			"workspace", "select",
+			"-no-color",
+			"workspace-name",
+		}, map[string]string{
+			"TF_REATTACH_PROVIDERS": `{"registry.terraform.io/hashicorp/examplecloud":{"Protocol":"grpc","ProtocolVersion":6,"Pid":1234,"Test":true,"Addr":{"Network":"unix","String":"/fake_folder/T/plugin123"}}}`,
+		}, workspaceSelectCmd)
+	})
+}
diff --git a/tfexec/workspace_show.go b/tfexec/workspace_show.go
index 840eff9..9ecfe47 100644
--- a/tfexec/workspace_show.go
+++ b/tfexec/workspace_show.go
@@ -10,9 +10,23 @@
 	"strings"
 )
 
+type workspaceShowConfig struct {
+	reattachInfo ReattachInfo
+}
+
+var defaultWorkspaceShowOptions = workspaceShowConfig{}
+
+type WorkspaceShowOption interface {
+	configureWorkspaceShow(*workspaceShowConfig)
+}
+
+func (opt *ReattachOption) configureWorkspaceShow(conf *workspaceShowConfig) {
+	conf.reattachInfo = opt.info
+}
+
 // WorkspaceShow represents the workspace show subcommand to the Terraform CLI.
-func (tf *Terraform) WorkspaceShow(ctx context.Context) (string, error) {
-	workspaceShowCmd, err := tf.workspaceShowCmd(ctx)
+func (tf *Terraform) WorkspaceShow(ctx context.Context, opts ...WorkspaceShowOption) (string, error) {
+	workspaceShowCmd, err := tf.workspaceShowCmd(ctx, opts...)
 	if err != nil {
 		return "", err
 	}
@@ -28,11 +42,26 @@
 	return strings.TrimSpace(outBuffer.String()), nil
 }
 
-func (tf *Terraform) workspaceShowCmd(ctx context.Context) (*exec.Cmd, error) {
+func (tf *Terraform) workspaceShowCmd(ctx context.Context, opts ...WorkspaceShowOption) (*exec.Cmd, error) {
 	err := tf.compatible(ctx, tf0_10_0, nil)
 	if err != nil {
 		return nil, fmt.Errorf("workspace show was first introduced in Terraform 0.10.0: %w", err)
 	}
 
-	return tf.buildTerraformCmd(ctx, nil, "workspace", "show", "-no-color"), nil
+	c := defaultWorkspaceShowOptions
+
+	for _, o := range opts {
+		o.configureWorkspaceShow(&c)
+	}
+
+	mergeEnv := map[string]string{}
+	if c.reattachInfo != nil {
+		reattachStr, err := c.reattachInfo.marshalString()
+		if err != nil {
+			return nil, err
+		}
+		mergeEnv[reattachEnvVar] = reattachStr
+	}
+
+	return tf.buildTerraformCmd(ctx, mergeEnv, "workspace", "show", "-no-color"), nil
 }
diff --git a/tfexec/workspace_show_test.go b/tfexec/workspace_show_test.go
index cf6fe8b..0ab3289 100644
--- a/tfexec/workspace_show_test.go
+++ b/tfexec/workspace_show_test.go
@@ -21,14 +21,42 @@
 	// empty env, to avoid environ mismatch in testing
 	tf.SetEnv(map[string]string{})
 
-	cmd, err := tf.workspaceShowCmd(context.Background())
-	if err != nil {
-		t.Fatal(err)
-	}
+	t.Run("defaults", func(t *testing.T) {
+		cmd, err := tf.workspaceShowCmd(context.Background())
+		if err != nil {
+			t.Fatal(err)
+		}
 
-	assertCmd(t, []string{
-		"workspace",
-		"show",
-		"-no-color",
-	}, map[string]string{}, cmd)
+		assertCmd(t, []string{
+			"workspace",
+			"show",
+			"-no-color",
+		}, nil, cmd)
+	})
+
+	t.Run("reattach config", func(t *testing.T) {
+		cmd, err := tf.workspaceShowCmd(context.Background(), Reattach(map[string]ReattachConfig{
+			"registry.terraform.io/hashicorp/examplecloud": {
+				Protocol:        "grpc",
+				ProtocolVersion: 6,
+				Pid:             1234,
+				Test:            true,
+				Addr: ReattachConfigAddr{
+					Network: "unix",
+					String:  "/fake_folder/T/plugin123",
+				},
+			},
+		}))
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assertCmd(t, []string{
+			"workspace",
+			"show",
+			"-no-color",
+		}, map[string]string{
+			"TF_REATTACH_PROVIDERS": `{"registry.terraform.io/hashicorp/examplecloud":{"Protocol":"grpc","ProtocolVersion":6,"Pid":1234,"Test":true,"Addr":{"Network":"unix","String":"/fake_folder/T/plugin123"}}}`,
+		}, cmd)
+	})
 }