From 22d8a09208c11b17f9e7e2dd58289eae297888c3 Mon Sep 17 00:00:00 2001
From: Samuel K <skevetter@pm.me>
Date: Sat, 6 Jun 2026 12:29:58 -0500
Subject: [PATCH 1/6] fix(provider): resync built-in provider configs from
 embedded YAML

Built-in provider configs persisted exec.command verbatim into
provider.json at init time, so a CLI command rename (helper sh ->
internal sh) left a stale wrapper that broke the inner agent commands
(e.g. workspace delete failed with a bare "exit status 1").

LoadProviderConfig now refreshes internal (Source.Internal) providers
from the embedded provider.yaml, which is the source of truth. A parse
failure of the embedded YAML is logged rather than silently falling
back to the stale stored config. Bumps all built-in providers to
v1.0.0 so a refreshed config is visibly newer than the legacy v0.0.1.
---
 pkg/provider/dir.go                | 30 +++++++++++
 pkg/provider/load_internal_test.go | 83 ++++++++++++++++++++++++++++++
 providers/docker/provider.yaml     |  2 +-
 providers/kubernetes/provider.yaml |  2 +-
 providers/podman/provider.yaml     |  2 +-
 providers/pro/provider.yaml        |  2 +-
 6 files changed, 117 insertions(+), 4 deletions(-)
 create mode 100644 pkg/provider/load_internal_test.go

diff --git a/pkg/provider/dir.go b/pkg/provider/dir.go
index 1a922c8a1..da7e3f182 100644
--- a/pkg/provider/dir.go
+++ b/pkg/provider/dir.go
@@ -12,6 +12,8 @@ import (
 	"github.com/devsy-org/devsy/pkg/config"
 	config2 "github.com/devsy-org/devsy/pkg/devcontainer/config"
 	"github.com/devsy-org/devsy/pkg/id"
+	"github.com/devsy-org/devsy/pkg/log"
+	"github.com/devsy-org/devsy/providers"
 )
 
 const (
@@ -303,9 +305,37 @@ func LoadProviderConfig(context, provider string) (*ProviderConfig, error) {
 		return nil, err
 	}
 
+	// Refresh built-in providers from the embedded YAML so a CLI change (e.g.
+	// renaming the command wrapper) can't leave a stale exec.command in the
+	// provider.json written at init time.
+	if providerConfig.Source.Internal {
+		if embedded := embeddedProviderConfig(providerConfig.Name); embedded != nil {
+			return embedded, nil
+		}
+	}
+
 	return providerConfig, nil
 }
 
+// embeddedProviderConfig parses the built-in provider definition for name,
+// returning nil when name is not built-in or the YAML fails to parse.
+func embeddedProviderConfig(name string) *ProviderConfig {
+	raw := providers.GetBuiltInProviders()[name]
+	if raw == "" {
+		return nil
+	}
+	parsed, err := ParseProvider(strings.NewReader(raw))
+	if err != nil {
+		// The YAML is go:embed-compiled, so a parse failure is a build-time
+		// regression. Log it: the silent fallback to the stored config would
+		// otherwise reintroduce the stale-command bug this refresh defeats.
+		log.Errorf("built-in provider %q failed to parse: %v", name, err)
+		return nil
+	}
+	parsed.Source.Internal = true
+	return parsed
+}
+
 func LoadMachineConfig(context, machineID string) (*Machine, error) {
 	machineDir, err := GetMachineDir(context, machineID)
 	if err != nil {
diff --git a/pkg/provider/load_internal_test.go b/pkg/provider/load_internal_test.go
new file mode 100644
index 000000000..29922b533
--- /dev/null
+++ b/pkg/provider/load_internal_test.go
@@ -0,0 +1,83 @@
+package provider
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/devsy-org/devsy/pkg/config"
+	"github.com/stretchr/testify/require"
+)
+
+// TestLoadProviderConfig_RefreshesInternalProvider verifies that a stored
+// built-in provider config with a stale exec.command (e.g. the removed
+// "helper sh" wrapper baked in before a CLI rename) is refreshed from the
+// embedded provider definition on load.
+func TestLoadProviderConfig_RefreshesInternalProvider(t *testing.T) {
+	setupTestHome(t)
+
+	stale := &ProviderConfig{
+		Name:    "docker",
+		Version: "v0.0.0-stale",
+		Source:  ProviderSource{Internal: true, Raw: "docker"},
+		Exec: ProviderCommands{
+			Command: []string{`"${DEVSY}" helper sh -c "${COMMAND}"`},
+		},
+	}
+	require.NoError(t, SaveProviderConfig(config.DefaultContext, stale))
+
+	loaded, err := LoadProviderConfig(config.DefaultContext, "docker")
+	require.NoError(t, err)
+	require.Len(t, loaded.Exec.Command, 1)
+	require.NotContains(t, loaded.Exec.Command[0], "helper sh",
+		"internal provider must be refreshed from embedded yaml, not the stale stored config")
+	require.True(t, strings.Contains(loaded.Exec.Command[0], "internal sh"),
+		"refreshed command should use the current 'internal sh' wrapper")
+	// The embedded YAML is the source of truth for internal providers, so the
+	// whole definition (not just exec.command) is taken from embed: the stale
+	// stored Version is replaced and Source.Internal stays set.
+	require.NotEqual(t, "v0.0.0-stale", loaded.Version,
+		"internal refresh replaces the stored definition wholesale, including Version")
+	require.True(t, loaded.Source.Internal, "refreshed provider must remain internal")
+}
+
+// TestLoadProviderConfig_UnknownInternalFallsBack ensures an internal provider
+// whose name is not a built-in (e.g. a removed/renamed provider lingering on
+// disk) falls back to the stored config rather than failing or returning empty.
+func TestLoadProviderConfig_UnknownInternalFallsBack(t *testing.T) {
+	setupTestHome(t)
+
+	stored := &ProviderConfig{
+		Name:    "retired-provider",
+		Version: "v0.0.1",
+		Source:  ProviderSource{Internal: true, Raw: "retired-provider"},
+		Exec: ProviderCommands{
+			Command: []string{`"${DEVSY}" internal sh -c "${COMMAND}"`},
+		},
+	}
+	require.NoError(t, SaveProviderConfig(config.DefaultContext, stored))
+
+	loaded, err := LoadProviderConfig(config.DefaultContext, "retired-provider")
+	require.NoError(t, err)
+	require.Equal(t, stored.Exec.Command, loaded.Exec.Command)
+	require.Equal(t, "v0.0.1", loaded.Version)
+}
+
+// TestLoadProviderConfig_PreservesExternalProvider ensures non-internal
+// providers are loaded verbatim from disk (no embedded refresh).
+func TestLoadProviderConfig_PreservesExternalProvider(t *testing.T) {
+	setupTestHome(t)
+
+	external := &ProviderConfig{
+		Name:    "docker",
+		Version: "v0.0.1",
+		Source:  ProviderSource{Github: "some-org/some-provider"},
+		Exec: ProviderCommands{
+			Command: []string{`"${DEVSY}" helper sh -c "${COMMAND}"`},
+		},
+	}
+	require.NoError(t, SaveProviderConfig(config.DefaultContext, external))
+
+	loaded, err := LoadProviderConfig(config.DefaultContext, "docker")
+	require.NoError(t, err)
+	require.Equal(t, external.Exec.Command, loaded.Exec.Command)
+}
diff --git a/providers/docker/provider.yaml b/providers/docker/provider.yaml
index 90037881a..75085908c 100644
--- a/providers/docker/provider.yaml
+++ b/providers/docker/provider.yaml
@@ -1,5 +1,5 @@
 name: docker
-version: v0.0.1
+version: v1.0.0
 icon: https://devsy.sh/assets/docker.svg
 home: https://github.com/devsy-org/devsy
 description: |-
diff --git a/providers/kubernetes/provider.yaml b/providers/kubernetes/provider.yaml
index be120bd83..965c968cd 100644
--- a/providers/kubernetes/provider.yaml
+++ b/providers/kubernetes/provider.yaml
@@ -1,5 +1,5 @@
 name: kubernetes
-version: v0.0.1
+version: v1.0.0
 icon: https://devsy.sh/assets/kubernetes.svg
 home: https://github.com/devsy-org/devsy
 description: |-
diff --git a/providers/podman/provider.yaml b/providers/podman/provider.yaml
index f8533ffaa..60ba8f810 100644
--- a/providers/podman/provider.yaml
+++ b/providers/podman/provider.yaml
@@ -1,5 +1,5 @@
 name: podman
-version: v0.0.1
+version: v1.0.0
 icon: https://devsy.sh/assets/podman.svg
 home: https://github.com/devsy-org/devsy
 description: |-
diff --git a/providers/pro/provider.yaml b/providers/pro/provider.yaml
index d7fa41c9a..fbe4401bc 100644
--- a/providers/pro/provider.yaml
+++ b/providers/pro/provider.yaml
@@ -1,5 +1,5 @@
 name: devsy-pro
-version: v0.0.1
+version: v1.0.0
 icon: https://devsy.sh/assets/devsy.svg
 home: https://github.com/devsy-org/devsy
 description: Devsy Pro

From 4c28ac5d1edd6ec723c71c21cef8fa734860b8af Mon Sep 17 00:00:00 2001
From: Samuel K <skevetter@pm.me>
Date: Sat, 6 Jun 2026 12:47:36 -0500
Subject: [PATCH 2/6] style(provider): use DockerDriver constant in tests

Replace repeated "docker" string literals with the existing
DockerDriver constant to satisfy goconst.
---
 pkg/provider/load_internal_test.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/pkg/provider/load_internal_test.go b/pkg/provider/load_internal_test.go
index 29922b533..e41d9a275 100644
--- a/pkg/provider/load_internal_test.go
+++ b/pkg/provider/load_internal_test.go
@@ -16,16 +16,16 @@ func TestLoadProviderConfig_RefreshesInternalProvider(t *testing.T) {
 	setupTestHome(t)
 
 	stale := &ProviderConfig{
-		Name:    "docker",
+		Name:    DockerDriver,
 		Version: "v0.0.0-stale",
-		Source:  ProviderSource{Internal: true, Raw: "docker"},
+		Source:  ProviderSource{Internal: true, Raw: DockerDriver},
 		Exec: ProviderCommands{
 			Command: []string{`"${DEVSY}" helper sh -c "${COMMAND}"`},
 		},
 	}
 	require.NoError(t, SaveProviderConfig(config.DefaultContext, stale))
 
-	loaded, err := LoadProviderConfig(config.DefaultContext, "docker")
+	loaded, err := LoadProviderConfig(config.DefaultContext, DockerDriver)
 	require.NoError(t, err)
 	require.Len(t, loaded.Exec.Command, 1)
 	require.NotContains(t, loaded.Exec.Command[0], "helper sh",
@@ -68,7 +68,7 @@ func TestLoadProviderConfig_PreservesExternalProvider(t *testing.T) {
 	setupTestHome(t)
 
 	external := &ProviderConfig{
-		Name:    "docker",
+		Name:    DockerDriver,
 		Version: "v0.0.1",
 		Source:  ProviderSource{Github: "some-org/some-provider"},
 		Exec: ProviderCommands{
@@ -77,7 +77,7 @@ func TestLoadProviderConfig_PreservesExternalProvider(t *testing.T) {
 	}
 	require.NoError(t, SaveProviderConfig(config.DefaultContext, external))
 
-	loaded, err := LoadProviderConfig(config.DefaultContext, "docker")
+	loaded, err := LoadProviderConfig(config.DefaultContext, DockerDriver)
 	require.NoError(t, err)
 	require.Equal(t, external.Exec.Command, loaded.Exec.Command)
 }

From f097123a7a75ddb3abf21facaead78e7dd7e0f74 Mon Sep 17 00:00:00 2001
From: Samuel K <skevetter@pm.me>
Date: Sat, 6 Jun 2026 13:26:12 -0500
Subject: [PATCH 3/6] docs(provider): trim verbose comments in
 internal-provider refresh

---
 pkg/provider/dir.go                | 10 ++++------
 pkg/provider/load_internal_test.go |  5 ++---
 2 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/pkg/provider/dir.go b/pkg/provider/dir.go
index da7e3f182..f053f95ef 100644
--- a/pkg/provider/dir.go
+++ b/pkg/provider/dir.go
@@ -305,9 +305,8 @@ func LoadProviderConfig(context, provider string) (*ProviderConfig, error) {
 		return nil, err
 	}
 
-	// Refresh built-in providers from the embedded YAML so a CLI change (e.g.
-	// renaming the command wrapper) can't leave a stale exec.command in the
-	// provider.json written at init time.
+	// Refresh built-in providers from embedded YAML: a CLI change (e.g. renaming
+	// the command wrapper) must not be shadowed by a stale provider.json.
 	if providerConfig.Source.Internal {
 		if embedded := embeddedProviderConfig(providerConfig.Name); embedded != nil {
 			return embedded, nil
@@ -326,9 +325,8 @@ func embeddedProviderConfig(name string) *ProviderConfig {
 	}
 	parsed, err := ParseProvider(strings.NewReader(raw))
 	if err != nil {
-		// The YAML is go:embed-compiled, so a parse failure is a build-time
-		// regression. Log it: the silent fallback to the stored config would
-		// otherwise reintroduce the stale-command bug this refresh defeats.
+		// Embedded YAML failing to parse is a build-time regression; log rather
+		// than silently fall back to the stale stored config this refresh fixes.
 		log.Errorf("built-in provider %q failed to parse: %v", name, err)
 		return nil
 	}
diff --git a/pkg/provider/load_internal_test.go b/pkg/provider/load_internal_test.go
index e41d9a275..71799d475 100644
--- a/pkg/provider/load_internal_test.go
+++ b/pkg/provider/load_internal_test.go
@@ -32,9 +32,8 @@ func TestLoadProviderConfig_RefreshesInternalProvider(t *testing.T) {
 		"internal provider must be refreshed from embedded yaml, not the stale stored config")
 	require.True(t, strings.Contains(loaded.Exec.Command[0], "internal sh"),
 		"refreshed command should use the current 'internal sh' wrapper")
-	// The embedded YAML is the source of truth for internal providers, so the
-	// whole definition (not just exec.command) is taken from embed: the stale
-	// stored Version is replaced and Source.Internal stays set.
+	// Refresh replaces the whole definition from embed, not just exec.command,
+	// so the stale stored Version is dropped and Source.Internal stays set.
 	require.NotEqual(t, "v0.0.0-stale", loaded.Version,
 		"internal refresh replaces the stored definition wholesale, including Version")
 	require.True(t, loaded.Source.Internal, "refreshed provider must remain internal")

From 3cd31ad257620b116bc5ab0f07a114059e6b4931 Mon Sep 17 00:00:00 2001
From: Samuel K <skevetter@pm.me>
Date: Sat, 6 Jun 2026 13:50:32 -0500
Subject: [PATCH 4/6] fix(provider): refresh built-ins by source id, not name

The built-in provider map is keyed by source id (e.g. "pro"), but some
providers have a different Name ("pro" -> "devsy-pro"). Keying the refresh
lookup off providerConfig.Name missed those, leaving a stale provider.json.
Use Source.Raw (the source id) instead.
---
 pkg/provider/dir.go                | 14 ++++++++------
 pkg/provider/load_internal_test.go | 24 ++++++++++++++++++++++++
 2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/pkg/provider/dir.go b/pkg/provider/dir.go
index f053f95ef..f9fede5a2 100644
--- a/pkg/provider/dir.go
+++ b/pkg/provider/dir.go
@@ -308,7 +308,7 @@ func LoadProviderConfig(context, provider string) (*ProviderConfig, error) {
 	// Refresh built-in providers from embedded YAML: a CLI change (e.g. renaming
 	// the command wrapper) must not be shadowed by a stale provider.json.
 	if providerConfig.Source.Internal {
-		if embedded := embeddedProviderConfig(providerConfig.Name); embedded != nil {
+		if embedded := embeddedProviderConfig(providerConfig.Source.Raw); embedded != nil {
 			return embedded, nil
 		}
 	}
@@ -316,10 +316,12 @@ func LoadProviderConfig(context, provider string) (*ProviderConfig, error) {
 	return providerConfig, nil
 }
 
-// embeddedProviderConfig parses the built-in provider definition for name,
-// returning nil when name is not built-in or the YAML fails to parse.
-func embeddedProviderConfig(name string) *ProviderConfig {
-	raw := providers.GetBuiltInProviders()[name]
+// embeddedProviderConfig parses the built-in provider definition for the given
+// source key (e.g. "pro"), returning nil when it is not built-in or the YAML
+// fails to parse. The built-in map is keyed by source id, which differs from
+// the provider Name for some providers (pro -> name "devsy-pro").
+func embeddedProviderConfig(sourceID string) *ProviderConfig {
+	raw := providers.GetBuiltInProviders()[sourceID]
 	if raw == "" {
 		return nil
 	}
@@ -327,7 +329,7 @@ func embeddedProviderConfig(name string) *ProviderConfig {
 	if err != nil {
 		// Embedded YAML failing to parse is a build-time regression; log rather
 		// than silently fall back to the stale stored config this refresh fixes.
-		log.Errorf("built-in provider %q failed to parse: %v", name, err)
+		log.Errorf("built-in provider %q failed to parse: %v", sourceID, err)
 		return nil
 	}
 	parsed.Source.Internal = true
diff --git a/pkg/provider/load_internal_test.go b/pkg/provider/load_internal_test.go
index 71799d475..b5b7769d1 100644
--- a/pkg/provider/load_internal_test.go
+++ b/pkg/provider/load_internal_test.go
@@ -39,6 +39,30 @@ func TestLoadProviderConfig_RefreshesInternalProvider(t *testing.T) {
 	require.True(t, loaded.Source.Internal, "refreshed provider must remain internal")
 }
 
+// TestLoadProviderConfig_RefreshesProBySourceID guards the case where a
+// built-in's provider Name differs from its source id: pro is stored as
+// "devsy-pro" but keyed in the embedded map as "pro". Refresh must key off
+// Source.Raw (the source id), not Name, or the pro provider keeps a stale config.
+func TestLoadProviderConfig_RefreshesProBySourceID(t *testing.T) {
+	setupTestHome(t)
+
+	stale := &ProviderConfig{
+		Name:    "devsy-pro",
+		Version: "v0.0.0-stale",
+		Source:  ProviderSource{Internal: true, Raw: "pro"},
+		Exec: ProviderCommands{
+			Command: []string{`"${DEVSY}" helper sh -c "${COMMAND}"`},
+		},
+	}
+	require.NoError(t, SaveProviderConfig(config.DefaultContext, stale))
+
+	loaded, err := LoadProviderConfig(config.DefaultContext, "devsy-pro")
+	require.NoError(t, err)
+	require.NotEqual(t, "v0.0.0-stale", loaded.Version,
+		"pro provider must be refreshed from embedded yaml via Source.Raw")
+	require.True(t, loaded.Source.Internal)
+}
+
 // TestLoadProviderConfig_UnknownInternalFallsBack ensures an internal provider
 // whose name is not a built-in (e.g. a removed/renamed provider lingering on
 // disk) falls back to the stored config rather than failing or returning empty.

From 3e3f57c270d455e551be8c776a7cc3f094c32973 Mon Sep 17 00:00:00 2001
From: Samuel K <skevetter@pm.me>
Date: Sat, 6 Jun 2026 14:15:37 -0500
Subject: [PATCH 5/6] style(provider): extract stale helper-sh command constant

The new pro refresh test pushed the "helper sh" exec.command literal to
3 occurrences, tripping goconst. Hoist it to a named constant.
---
 pkg/provider/load_internal_test.go | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/pkg/provider/load_internal_test.go b/pkg/provider/load_internal_test.go
index b5b7769d1..e741ff8f8 100644
--- a/pkg/provider/load_internal_test.go
+++ b/pkg/provider/load_internal_test.go
@@ -8,6 +8,10 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+// staleHelperShCommand is the removed "helper sh" exec.command wrapper that
+// stale provider.json files carry before a refresh rewrites them.
+const staleHelperShCommand = `"${DEVSY}" helper sh -c "${COMMAND}"`
+
 // TestLoadProviderConfig_RefreshesInternalProvider verifies that a stored
 // built-in provider config with a stale exec.command (e.g. the removed
 // "helper sh" wrapper baked in before a CLI rename) is refreshed from the
@@ -20,7 +24,7 @@ func TestLoadProviderConfig_RefreshesInternalProvider(t *testing.T) {
 		Version: "v0.0.0-stale",
 		Source:  ProviderSource{Internal: true, Raw: DockerDriver},
 		Exec: ProviderCommands{
-			Command: []string{`"${DEVSY}" helper sh -c "${COMMAND}"`},
+			Command: []string{staleHelperShCommand},
 		},
 	}
 	require.NoError(t, SaveProviderConfig(config.DefaultContext, stale))
@@ -51,7 +55,7 @@ func TestLoadProviderConfig_RefreshesProBySourceID(t *testing.T) {
 		Version: "v0.0.0-stale",
 		Source:  ProviderSource{Internal: true, Raw: "pro"},
 		Exec: ProviderCommands{
-			Command: []string{`"${DEVSY}" helper sh -c "${COMMAND}"`},
+			Command: []string{staleHelperShCommand},
 		},
 	}
 	require.NoError(t, SaveProviderConfig(config.DefaultContext, stale))
@@ -95,7 +99,7 @@ func TestLoadProviderConfig_PreservesExternalProvider(t *testing.T) {
 		Version: "v0.0.1",
 		Source:  ProviderSource{Github: "some-org/some-provider"},
 		Exec: ProviderCommands{
-			Command: []string{`"${DEVSY}" helper sh -c "${COMMAND}"`},
+			Command: []string{staleHelperShCommand},
 		},
 	}
 	require.NoError(t, SaveProviderConfig(config.DefaultContext, external))

From 1e34a49b9bb4b94bd8e6e5bb95b4f59a9dcb0fbb Mon Sep 17 00:00:00 2001
From: Samuel K <skevetter@pm.me>
Date: Sat, 6 Jun 2026 14:41:06 -0500
Subject: [PATCH 6/6] fix(provider): overlay only exec on internal refresh

Wholesale-replacing the stored config with the embedded definition clobbered
user customizations: a built-in added with a custom --name or --option (e.g.
`devsy provider add docker --name foo`) lost its Name and resolved Options on
load, breaking `devsy up`. Only the exec/agent.exec blocks carry the stale
command wrapper, so overlay just those and keep the rest of the stored config.
---
 pkg/provider/dir.go                | 10 +++---
 pkg/provider/load_internal_test.go | 55 ++++++++++++++++++++++--------
 2 files changed, 46 insertions(+), 19 deletions(-)

diff --git a/pkg/provider/dir.go b/pkg/provider/dir.go
index f9fede5a2..c42fa46cd 100644
--- a/pkg/provider/dir.go
+++ b/pkg/provider/dir.go
@@ -305,11 +305,14 @@ func LoadProviderConfig(context, provider string) (*ProviderConfig, error) {
 		return nil, err
 	}
 
-	// Refresh built-in providers from embedded YAML: a CLI change (e.g. renaming
-	// the command wrapper) must not be shadowed by a stale provider.json.
+	// Refresh the exec commands of built-in providers from embedded YAML: a CLI
+	// change (e.g. renaming the command wrapper) must not be shadowed by a stale
+	// provider.json. Only the exec blocks are overlaid so user customizations
+	// (custom --name, resolved options) survive.
 	if providerConfig.Source.Internal {
 		if embedded := embeddedProviderConfig(providerConfig.Source.Raw); embedded != nil {
-			return embedded, nil
+			providerConfig.Exec = embedded.Exec
+			providerConfig.Agent.Exec = embedded.Agent.Exec
 		}
 	}
 
@@ -332,7 +335,6 @@ func embeddedProviderConfig(sourceID string) *ProviderConfig {
 		log.Errorf("built-in provider %q failed to parse: %v", sourceID, err)
 		return nil
 	}
-	parsed.Source.Internal = true
 	return parsed
 }
 
diff --git a/pkg/provider/load_internal_test.go b/pkg/provider/load_internal_test.go
index e741ff8f8..5cc0053f7 100644
--- a/pkg/provider/load_internal_test.go
+++ b/pkg/provider/load_internal_test.go
@@ -5,6 +5,7 @@ import (
 	"testing"
 
 	"github.com/devsy-org/devsy/pkg/config"
+	"github.com/devsy-org/devsy/pkg/types"
 	"github.com/stretchr/testify/require"
 )
 
@@ -12,16 +13,18 @@ import (
 // stale provider.json files carry before a refresh rewrites them.
 const staleHelperShCommand = `"${DEVSY}" helper sh -c "${COMMAND}"`
 
+const staleVersion = "v0.0.0-stale"
+
 // TestLoadProviderConfig_RefreshesInternalProvider verifies that a stored
 // built-in provider config with a stale exec.command (e.g. the removed
-// "helper sh" wrapper baked in before a CLI rename) is refreshed from the
-// embedded provider definition on load.
+// "helper sh" wrapper baked in before a CLI rename) has its exec block
+// refreshed from the embedded provider definition on load.
 func TestLoadProviderConfig_RefreshesInternalProvider(t *testing.T) {
 	setupTestHome(t)
 
 	stale := &ProviderConfig{
 		Name:    DockerDriver,
-		Version: "v0.0.0-stale",
+		Version: staleVersion,
 		Source:  ProviderSource{Internal: true, Raw: DockerDriver},
 		Exec: ProviderCommands{
 			Command: []string{staleHelperShCommand},
@@ -36,11 +39,35 @@ func TestLoadProviderConfig_RefreshesInternalProvider(t *testing.T) {
 		"internal provider must be refreshed from embedded yaml, not the stale stored config")
 	require.True(t, strings.Contains(loaded.Exec.Command[0], "internal sh"),
 		"refreshed command should use the current 'internal sh' wrapper")
-	// Refresh replaces the whole definition from embed, not just exec.command,
-	// so the stale stored Version is dropped and Source.Internal stays set.
-	require.NotEqual(t, "v0.0.0-stale", loaded.Version,
-		"internal refresh replaces the stored definition wholesale, including Version")
-	require.True(t, loaded.Source.Internal, "refreshed provider must remain internal")
+}
+
+// TestLoadProviderConfig_PreservesCustomizations verifies the exec refresh
+// overlays only the embedded exec block, leaving a user's custom Name and
+// resolved Options intact (a built-in added with `--name`/`--option`).
+func TestLoadProviderConfig_PreservesCustomizations(t *testing.T) {
+	setupTestHome(t)
+
+	stored := &ProviderConfig{
+		Name:    "my-docker",
+		Version: staleVersion,
+		Source:  ProviderSource{Internal: true, Raw: DockerDriver},
+		Options: map[string]*types.Option{
+			"DOCKER_PATH": {Default: "podman"},
+		},
+		Exec: ProviderCommands{
+			Command: []string{staleHelperShCommand},
+		},
+	}
+	require.NoError(t, SaveProviderConfig(config.DefaultContext, stored))
+
+	loaded, err := LoadProviderConfig(config.DefaultContext, "my-docker")
+	require.NoError(t, err)
+	require.Equal(t, "my-docker", loaded.Name, "custom provider name must survive refresh")
+	require.Equal(t, "v0.0.0-stale", loaded.Version, "stored version must survive refresh")
+	require.Equal(t, "podman", loaded.Options["DOCKER_PATH"].Default,
+		"resolved options must survive refresh")
+	require.NotContains(t, loaded.Exec.Command[0], "helper sh",
+		"exec block must still be refreshed from embedded yaml")
 }
 
 // TestLoadProviderConfig_RefreshesProBySourceID guards the case where a
@@ -52,19 +79,17 @@ func TestLoadProviderConfig_RefreshesProBySourceID(t *testing.T) {
 
 	stale := &ProviderConfig{
 		Name:    "devsy-pro",
-		Version: "v0.0.0-stale",
+		Version: staleVersion,
 		Source:  ProviderSource{Internal: true, Raw: "pro"},
-		Exec: ProviderCommands{
-			Command: []string{staleHelperShCommand},
-		},
+		Exec:    ProviderCommands{Daemon: &DaemonCommands{Start: []string{"stale start"}}},
 	}
 	require.NoError(t, SaveProviderConfig(config.DefaultContext, stale))
 
 	loaded, err := LoadProviderConfig(config.DefaultContext, "devsy-pro")
 	require.NoError(t, err)
-	require.NotEqual(t, "v0.0.0-stale", loaded.Version,
-		"pro provider must be refreshed from embedded yaml via Source.Raw")
-	require.True(t, loaded.Source.Internal)
+	require.NotNil(t, loaded.Exec.Daemon, "pro exec must be refreshed from embedded yaml")
+	require.NotEqual(t, []string{"stale start"}, []string(loaded.Exec.Daemon.Start),
+		"pro provider exec must be refreshed from embedded yaml via Source.Raw")
 }
 
 // TestLoadProviderConfig_UnknownInternalFallsBack ensures an internal provider
