Skip to content

Conversation

@blstjohnson
Copy link

@blstjohnson blstjohnson commented Jan 24, 2026

New feature to rename providers #274

new command for provider - rename accepting old name and new name, that will validate new name and change the name of the provider
new command workspace rebind - to rebind workspace to a new provider, runs inside provider rename, but can also be used to transfer workspace to a different provider

Summary by CodeRabbit

  • New Features

    • Provider rename: CLI and desktop support to rename providers, auto-rebind associated workspaces, update defaults, and rollback on failure.
    • Workspace command: new workspace command with a rebind subcommand to move a workspace to a different provider.
  • UI

    • Provider name input with validation (lowercase, numbers, hyphens, 32-char max); integrated rename flow and routing with optimistic updates.
  • API

    • Desktop client: added rename operation and command wiring.
  • Documentation

    • New guide detailing rename workflows.
  • Tests

    • Added e2e tests covering provider rename and workspace rebind.
  • Chores

    • Clarified CLI flag help text for provider naming.

@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds provider rename and workspace rebind capabilities across CLI, backend, frontend, docs, and E2E tests: new provider rename and workspace rebind commands, workspace SwitchProvider API, desktop rename UI + optimistic cache handling, docs, and automated tests/helpers.

Changes

Cohort / File(s) Summary
Provider CLI
cmd/provider/rename.go, cmd/provider/provider.go, cmd/provider/add.go
Adds provider rename command implementing clone → rebind → default-update → cleanup/rollback flow; wires command into provider CLI; minor --name flag help-text change.
Workspace CLI / Root
cmd/workspace/workspace.go, cmd/workspace/rebind.go, cmd/root.go
Adds workspace command and workspace rebind <workspace> <new-provider> subcommand; registers workspace command on root.
Backend — workspace API
pkg/workspace/provider.go
Adds SwitchProvider(ctx, devPodConfig, workspace, newProviderName) which locks the workspace client, validates stopped state, updates provider name, persists change, and rolls back on save failure.
Frontend — client & commands
desktop/src/client/constants.ts, desktop/src/client/providers/client.ts, desktop/src/client/providers/providerCommands.ts
Introduces DEVPOD_COMMAND_RENAME, ProvidersClient.rename, and ProviderCommands.RenameProvider to invoke CLI rename from the desktop app.
Frontend — validation, types & state
desktop/src/lib/validation.ts, desktop/src/views/Providers/AddProvider/helpers.ts, desktop/src/types.ts, desktop/src/contexts/DevPodContext/useProviderManager.ts
Adds PROVIDER_NAME_REGEX and MAX_PROVIDER_NAME_LENGTH, simplifies local regex, adds rename to TProviderManager, and implements optimistic renameMutation with rollback and cache invalidation.
Frontend — UI & forms
desktop/src/views/Providers/AddProvider/ConfigureProviderOptionsForm.tsx, desktop/src/views/Providers/AddProvider/SetupProviderSourceForm.tsx, desktop/src/views/Providers/Provider.tsx
Adds provider Name input + validation; integrates rename flow on save (calls rename API and navigates to new provider route); inlines Name field in setup flow; passes name prop into Provider.
E2E framework & suites
e2e/e2e_suite_test.go, e2e/framework/command.go, e2e/tests/workspace/workspace.go, e2e/tests/workspace/workspace_suite_test.go
Registers workspace tests; adds test helpers DevPodProviderRename and DevPodWorkspaceRebind; adds Ginkgo bootstrap for workspace tests.
E2E tests
e2e/tests/workspace/rebind_test.go, e2e/tests/provider/provider.go
Adds workspace rebind E2E tests (success and failure cases) and provider rename test scenarios (provider tests include duplicated block).
Docs & sidebar
docs/pages/managing-providers/rename-provider.md, docs/sidebars.js
Adds documentation page describing CLI/GUI rename behavior and rollback; registers sidebar entry under "Managing your Providers".
Misc UI helpers
desktop/src/views/Providers/AddProvider/helpers.ts
Adjusted ALLOWED_NAMES_REGEX literal (removed unnecessary escape).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant Config
    participant ProviderMgr
    participant Workspaces

    User->>CLI: devpod provider rename oldName newName
    CLI->>Config: Load config & validate newName
    CLI->>Config: Ensure newName absent

    rect rgba(100,150,200,0.5)
    CLI->>ProviderMgr: Clone provider oldName -> newName
    ProviderMgr->>Config: Persist new provider
    end

    CLI->>Config: List workspaces bound to oldName
    loop For each workspace
        CLI->>Workspaces: Load workspace config
        CLI->>Workspaces: Update workspace provider -> newName
        Workspaces->>Config: Save workspace config
    end

    rect rgba(100,200,150,0.5)
    CLI->>Config: If default == oldName set default -> newName
    CLI->>ProviderMgr: Delete oldName provider
    CLI->>User: Success
    end

    rect rgba(200,150,100,0.5)
    CLI->>Workspaces: On error revert workspace bindings -> oldName
    CLI->>Config: Restore default if changed
    CLI->>ProviderMgr: Delete cloned newName provider
    CLI->>User: Failure — rolled back
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature #274 Rename provider' clearly and specifically describes the main change: adding a provider rename feature. It is concise, relates directly to the changeset, and conveys the primary functionality being added.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
docs/pages/managing-providers/rename-provider.md (2)

23-23: Consider varying the phrasing for improved readability.

The phrase "you want to rename" appears twice in close proximity. While this doesn't affect clarity, you could vary the wording slightly for better flow.

✍️ Alternative wording suggestion
-If you have a provider named `my-docker` and you want to rename it to `local-docker`, you would run:
+If you have a provider named `my-docker` and wish to rename it to `local-docker`, you would run:

Or simply:

-If you have a provider named `my-docker` and you want to rename it to `local-docker`, you would run:
+To rename a provider called `my-docker` to `local-docker`, run:

16-20: Consider documenting provider name constraints.

While the current argument descriptions are clear, users might benefit from knowing upfront what constitutes a valid provider name (e.g., allowed characters, length limits, whether the new name must be unique). This could help prevent confusion if a rename operation fails due to validation errors.

📝 Example addition

You could add a note after the arguments section, such as:

### Name Requirements

- Provider names must follow the naming pattern defined by DevPod
- The new name must not conflict with an existing provider
- Names are validated before the rename operation begins

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
desktop/src/views/Providers/AddProvider/SetupProviderSourceForm.tsx (1)

53-53: Regex bug: double backslash creates incorrect pattern.

The regex /^[a-z0-9\\-]+$/ uses a double backslash, which in a regex literal matches a literal backslash character followed by a hyphen—not just a hyphen. This will reject valid names like my-provider and accept invalid names like my\-provider.

Additionally, this duplicates ALLOWED_NAMES_REGEX already exported from helpers.ts. Import it instead.

🐛 Proposed fix

Remove the local definition and import from helpers:

-import { exists, isError, randomString, useFormErrors } from "../../../lib"
+import { exists, isError, randomString, useFormErrors, ALLOWED_NAMES_REGEX } from "../../../lib"

Then remove line 53:

-const ALLOWED_NAMES_REGEX = /^[a-z0-9\\-]+$/

Alternatively, if ALLOWED_NAMES_REGEX is not re-exported from ../../../lib, import directly from ./helpers:

-import { FieldName, TFormValues, TSetupProviderResult } from "./types"
+import { FieldName, TFormValues, TSetupProviderResult } from "./types"
+import { ALLOWED_NAMES_REGEX } from "./helpers"
🤖 Fix all issues with AI agents
In `@cmd/provider/rename.go`:
- Around line 43-47: Update the help/warning text for the "provider rename"
command (the help block in cmd/provider/rename.go used by the provider rename
command) to remove the instruction that users must manually edit workspaces and
instead state that the command will attempt to automatically rebind workspaces
to the new provider name, while advising users to verify workspace configs and
note any potential edge cases/failures that may require manual intervention;
ensure the revised wording replaces the existing WARNING paragraph so it clearly
reflects automatic rebinding and suggests verification steps.
- Around line 86-100: The loop that updates workspaces currently logs failures
from provider.SaveWorkspaceConfig and continues, then calls
provider.RenameProvider which can orphan workspaces; change the logic in the
block iterating workspaces so that any error returned by
provider.SaveWorkspaceConfig for a workspace causes the command to abort (or
accumulate and return an aggregated error) before calling
provider.RenameProvider; specifically, in the loop over workspaces where you
check ws.Provider.Name == oldName, stop and return the error (or collect it and
return after the loop) instead of only logging in the
ws.Provider.SaveWorkspaceConfig error path so provider.RenameProvider is only
called when all workspace rebinds succeeded.

In `@cmd/workspace/rebind.go`:
- Around line 36-46: The code is passing the raw workspace name to
provider.LoadWorkspaceConfig (and later to the save path) but
provider.LoadWorkspaceConfig expects a workspace ID; convert the name to an ID
first using workspace.ToID and use that ID when calling
provider.LoadWorkspaceConfig(devPodConfig.DefaultContext, workspaceID) and when
later saving (e.g., provider.SaveWorkspaceConfig). Replace usages of
workspaceName in LoadWorkspaceConfig/SaveWorkspaceConfig calls with the
normalized workspaceID returned by workspace.ToID, and propagate any conversion
errors appropriately.

In `@desktop/src/views/Providers/AddProvider/ConfigureProviderOptionsForm.tsx`:
- Around line 105-118: The current handleSave sequence renames before
configuring which can leave the UI on the old route if configure fails; change
the sequence to call client.providers.configure(finalProviderID/config) first
(using providerID), then call client.providers.rename(providerID, newName) if
needed, update finalProviderID accordingly, invalidate QueryKeys.PROVIDERS after
both succeed, and call navigate(Routes.toProvider(newName), { replace: true })
only after rename succeeds; ensure you reference handleSave,
client.providers.configure, client.providers.rename, QueryKeys.PROVIDERS and
Routes.toProvider when making the change.

In `@desktop/src/views/Providers/AddProvider/SetupProviderSourceForm.tsx`:
- Around line 377-381: The validation message in SetupProviderSourceForm using
ALLOWED_NAMES_REGEX is misleading because the regex only allows lowercase
letters; update the user-facing error text to accurately reflect the rule (e.g.,
"Name can only contain lowercase letters, numbers and -") or alternatively
broaden ALLOWED_NAMES_REGEX to allow uppercase if uppercase should be permitted;
modify the message string next to the rules.pattern block (the rule inside
SetupProviderSourceForm.tsx) and ensure it matches the actual
ALLOWED_NAMES_REGEX behavior.

In `@e2e/tests/provider/provider.go`:
- Line 21: The test uses an incorrect relative path when calling
framework.CopyToTempDir, so update the string argument from
"testdata/simple-k8s-provider" to "tests/provider/testdata/simple-k8s-provider"
wherever it is used in this file (all four occurrences that call
framework.CopyToTempDir, e.g., the calls that assign tempDir and err); keep the
rest of the call and error handling intact.

In `@e2e/tests/workspace/rebind_test.go`:
- Around line 50-59: The test derives workspaceID with filepath.Base(tempDir)
but DevPod uses workspace.ToID() (see ToID in pkg/workspace) so Stop/Rebind/SSH
may target the wrong workspace; update the test to consistently transform the
derived ID using workspace.ToID() before passing it to f.DevPodStop and
f.DevPodWorkspaceRebind (or alternatively pass tempDir directly like
f.DevPodWorkspaceDelete does), ensuring the same identifier normalization used
by ToID() is applied when calling DevPodStop, DevPodWorkspaceRebind, and any
subsequent workspace operations.

In `@pkg/provider/dir.go`:
- Around line 453-466: Currently the code saves the new provider config then
deletes the old provider directory, which discards binaries/state; instead,
obtain oldProviderDir via GetProviderDir(context, oldName) and move it to the
new provider directory using os.Rename (or shutil-equivalent on cross-filesystem
if needed), then call SaveProviderConfig(context, providerConfig) to write
provider.json into the moved directory (or update provider.json in its new
path). Ensure you replace the final os.RemoveAll(oldProviderDir) with the rename
operation and update any path references so the provider daemon/binaries remain
intact.
- Around line 435-442: The current check treats any os.Stat error other than
IsNotExist as "already exists"; change the logic in the rename/create flow that
uses GetProviderDir, newProviderDir and os.Stat so you explicitly handle three
cases: if os.Stat returns nil then return the "provider with name %s already
exists" error; if os.IsNotExist(err) continue (allowed); otherwise return the
underlying filesystem error (wrap or propagate) so permission/I/O errors are
surfaced instead of being misclassified as "already exists".
♻️ Duplicate comments (1)
e2e/tests/provider/rename_test.go (1)

143-145: Workspace identifier mismatch risk (path vs ID).

Line 144 passes tempDir to DevPodSSH. If the helper expects a workspace ID (as used in other tests), this will be flaky. Align with the identifier the framework expects.

🧹 Nitpick comments (4)
e2e/framework/helper.go (1)

8-10: Consider differentiating from the existing DevPodDescribe in integration tests.

A DevPodDescribe function already exists in e2e/tests/integration/framework.go that adds an "[integration] " prefix to the description. This new generic version in the shared framework package has no prefix, which could cause confusion when both packages are imported or when developers expect consistent test output formatting.

Consider either:

  1. Adding a distinguishing prefix (e.g., "[workspace] ") for workspace tests
  2. Renaming this function to clarify its generic nature
  3. Removing the integration-specific version and having all tests use this generic one with explicit prefixes
e2e/framework/command.go (1)

391-395: Add context to rebind failures for easier triage.

Returning the raw error drops command context in test logs. Consider wrapping the error (and optionally stderr) to match other helpers.

♻️ Suggested tweak
 func (f *Framework) DevPodWorkspaceRebind(ctx context.Context, workspaceName, newProvider string, extraArgs ...string) error {
 	args := []string{"workspace", "rebind", workspaceName, newProvider}
 	args = append(args, extraArgs...)
-	_, _, err := f.ExecCommandCapture(ctx, args)
-	return err
+	_, _, err := f.ExecCommandCapture(ctx, args)
+	if err != nil {
+		return fmt.Errorf("devpod workspace rebind failed: %s", err.Error())
+	}
+	return nil
 }
desktop/src/views/Providers/AddProvider/ConfigureProviderOptionsForm.tsx (1)

150-151: Hide the name field when configuring workspace-specific options.

In the workspace flow, onSave is a noop, so editing the name currently does nothing. Consider rendering the name field only for provider-level configuration to avoid misleading the user.

♻️ Suggested UI guard
-          <FormControl>
-            <FormLabel>Provider Name</FormLabel>
-            <Input {...formMethods.register(FieldName.NAME)} />
-          </FormControl>
+          {workspace === undefined && (
+            <FormControl>
+              <FormLabel>Provider Name</FormLabel>
+              <Input {...formMethods.register(FieldName.NAME)} />
+            </FormControl>
+          )}

Also applies to: 166-167, 270-273

docs/pages/managing-providers/rename-provider.md (1)

39-39: Consider adding a warning about workspace impact.

The CLI help text in cmd/provider/rename.go includes an important warning: "WARNING: Renaming a provider will cause all workspaces bound to it to fail. You will have to manually edit the workspace configurations to use the new provider name."

This documentation should include a similar warning or note so users are aware of the potential impact on existing workspaces, particularly for CLI users who may not see the help text.

📝 Suggested addition
 After renaming, DevPod will automatically update the provider's configuration.
+
+:::warning
+Renaming a provider will cause all workspaces bound to it to fail. You may need to rebind workspaces to use the new provider name using `devpod workspace rebind`.
+:::

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
desktop/src/views/Providers/AddProvider/SetupProviderSourceForm.tsx (1)

53-53: Bug: Double backslash allows literal backslash characters in names.

The regex /^[a-z0-9\\-]+$/ uses \\- which in a JS regex literal matches a literal backslash plus hyphen. This differs from the existing ALLOWED_NAMES_REGEX in helpers.ts which correctly uses /^[a-z0-9\-]+$/.

Import from helpers.ts instead of duplicating and introducing this bug:

🔧 Suggested fix
-const ALLOWED_NAMES_REGEX = /^[a-z0-9\\-]+$/

Then update the import on line 45 to include ALLOWED_NAMES_REGEX:

import { exists, isError, randomString, useFormErrors, ALLOWED_NAMES_REGEX } from "../../../lib"

Or import directly from the helpers file if ALLOWED_NAMES_REGEX isn't re-exported through lib:

import { ALLOWED_NAMES_REGEX } from "./helpers"
🤖 Fix all issues with AI agents
In `@cmd/provider/rename.go`:
- Around line 90-112: The provider is being renamed before workspaces are
rebound which can orphan workspaces if any rebind fails; change the sequence so
you iterate workspacesToRebind and call provider.SaveWorkspaceConfig (updating
ws.Provider.Name = newName) for each workspace first, collecting rebindErrors
and successfulRebinds, and only if all required rebinding succeeds call
provider.RenameProvider(devPodConfig.DefaultContext, oldName, newName);
additionally, if you keep partial-rebind behavior, implement rollback logic to
restore any successfulRebinds back to oldName (using SaveWorkspaceConfig) when
provider.RenameProvider fails so no workspace is left referencing a non-existent
provider.
🧹 Nitpick comments (2)
cmd/provider/rename.go (1)

22-51: Excessive blank lines reduce readability.

Multiple unnecessary blank lines between statements (e.g., lines 23, 25, 30, 45, 47) create visual clutter. Consider removing the extra blank lines for a cleaner structure.

♻️ Suggested cleanup
 func NewRenameCmd(globalFlags *flags.GlobalFlags) *cobra.Command {
-
 	cmd := &RenameCmd{
-
 		GlobalFlags: globalFlags,
 	}

 	return &cobra.Command{
-
 		Use: "rename",
-
 		Short: "Rename a provider",
-
 		Long: `Renames a provider and automatically rebinds all workspaces
 that are bound to it to use the new provider name.

 Example:
   devpod provider rename my-provider my-new-provider
 `,
-
 		Args: cobra.ExactArgs(2),
-
 		RunE: func(cobraCmd *cobra.Command, args []string) error {
-
 			return cmd.Run(cobraCmd, args)
-
 		},
 	}
-
 }
desktop/src/views/Providers/AddProvider/ConfigureProviderOptionsForm.tsx (1)

272-275: Consider defaulting to providerID for better UX.

When name prop is undefined, the input renders empty. Users editing an existing provider may expect to see the current provider name. Consider defaulting to providerID when name is not provided.

♻️ Suggested default
   const formMethods = useForm<TFieldValues>({
     defaultValues: {
       reuseMachine,
       useAsDefault: isDefault,
-      name: name,
+      name: name ?? providerID,
     },
   })

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@cmd/provider/rename.go`:
- Around line 145-150: When renaming a provider, if oldName is currently the
default, switch the default to newName before calling DeleteProviderConfig to
avoid clearing the user's default selection; detect this by checking the current
default provider (e.g., compare stored defaultName to oldName) and call the
appropriate setter to update the default to newName, then proceed to call
DeleteProviderConfig(devPodConfig, oldName, true) and handle errors as before
(use DeleteProviderConfig, oldName, newName to locate the logic).

@blstjohnson blstjohnson requested a review from skevetter January 26, 2026 20:46
@skevetter
Copy link
Owner

Please fix failing checks first

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@cmd/provider/rename.go`:
- Around line 103-116: The rollback function currently returns immediately if
rebindWorkspaces(workspacesTouched, oldName) fails, leaving the cloned provider
(newName) orphaned; change rollback to always attempt
DeleteProviderConfig(devPodConfig, newName, true) even when rebindWorkspaces
returns an error, capture both errors (the rebind error and any delete error),
and return an aggregated error (or combined message) so callers know both
failures; reference the rollback function, the rebindWorkspaces call, and
DeleteProviderConfig invocation when making the change.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
desktop/src/views/Providers/AddProvider/SetupProviderSourceForm.tsx (1)

52-52: Regex allows unintended backslash characters in names.

The double backslash \\- in the character class matches a literal backslash character plus a hyphen. This means names like my\provider would pass validation, which could cause issues with file paths or command-line operations.

🐛 Suggested fix
-const ALLOWED_NAMES_REGEX = /^[a-z0-9\\-]+$/
+const ALLOWED_NAMES_REGEX = /^[a-z0-9-]+$/

A hyphen at the end of a character class doesn't require escaping.

desktop/src/views/Providers/AddProvider/ConfigureProviderOptionsForm.tsx (1)

150-167: Hide/guard Provider Name in workspace mode to avoid a no‑op rename.

In workspace mode, onSave is a no‑op, so the new Provider Name field gives the impression a rename will happen when it won’t. Also, passing an empty/whitespace name can lead to confusing UX or backend errors. Consider hiding (or disabling) the field when workspace is set and only forwarding a trimmed non-empty newName.

✅ Suggested fix
   const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
     // make sure we don't bubble up the event to the parent
     event.stopPropagation()
     event.preventDefault()

     formMethods.handleSubmit((data) => {
       const { useAsDefault, reuseMachine, name } = data
+      const trimmedName = typeof name === "string" ? name.trim() : undefined
+      const newName = trimmedName && trimmedName !== providerID ? trimmedName : undefined
       configureProvider({
         providerID,
         config: {
           reuseMachine: reuseMachine ?? false,
           useAsDefaultProvider: useAsDefault,
           options: filterOptions(data, allOptions),
         },
-        newName: name as string,
+        newName,
       })
     })(event)
   }
@@
-          <VStack align="start" spacing={8} position="relative" width="full">
-            <FormControl>
-              <FormLabel>Provider Name</FormLabel>
-              <Input {...formMethods.register(FieldName.NAME)} />
-            </FormControl>
+          <VStack align="start" spacing={8} position="relative" width="full">
+            {!workspace && (
+              <FormControl>
+                <FormLabel>Provider Name</FormLabel>
+                <Input {...formMethods.register(FieldName.NAME)} />
+              </FormControl>
+            )}

Also applies to: 221-236, 270-273

🧹 Nitpick comments (1)
desktop/src/views/Providers/AddProvider/SetupProviderSourceForm.tsx (1)

376-389: Consider using * quantifier for optional field pattern validation.

The pattern uses + (one or more), but this field is optional. While react-hook-form typically skips pattern validation for empty non-required fields, using * (zero or more) would make the intent explicit and guard against edge cases where the field value is an empty string.

♻️ Suggested refinement
                    rules={{
                      pattern: {
-                       value: ALLOWED_NAMES_REGEX,
+                       value: /^[a-z0-9-]*$/,
                        message: "Name can only contain lowercase letters, numbers and hyphens",
                      },

Alternatively, update ALLOWED_NAMES_REGEX to use * if other usages also need to allow empty strings.

@blstjohnson blstjohnson marked this pull request as draft January 27, 2026 20:17
@blstjohnson blstjohnson marked this pull request as ready for review January 28, 2026 10:19
@blstjohnson blstjohnson marked this pull request as draft January 28, 2026 13:01
@blstjohnson blstjohnson marked this pull request as ready for review January 28, 2026 15:29
Copy link
Owner

@skevetter skevetter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@cmd/provider/rename.go`:
- Around line 119-121: The error message returned in the provider rename
validation (where provider.ProviderNameRegEx.MatchString(newName) is checked)
uses "smaller case letters"; update that error string to use standard
terminology "lowercase letters" (e.g., change "provider name can only include
smaller case letters, numbers or dashes" to "provider name can only include
lowercase letters, numbers, or dashes") so the message is clearer and
consistent.
🧹 Nitpick comments (2)
pkg/workspace/provider.go (1)

443-460: Consider using defer for unlock to prevent resource leaks.

The current lock/unlock pattern doesn't use defer, which could leave the workspace locked if a panic occurs between Lock() and Unlock(). Additionally, errors from Unlock() are silently ignored.

♻️ Proposed refactor using defer
 	// Lock the workspace before switching provider
 	err = client.Lock(ctx)
 	if err != nil {
 		return fmt.Errorf("failed to lock workspace %s: %w", workspace.ID, err)
 	}
+	defer client.Unlock()

 	// Update the provider name in the workspace configuration
 	workspace.Provider.Name = newProviderName

 	// Save the updated configuration
 	err = providerpkg.SaveWorkspaceConfig(workspace)
 	if err != nil {
-		client.Unlock() // Unlock before returning error
 		return fmt.Errorf("failed to save workspace config: %w", err)
 	}

-	// Unlock after successful save
-	client.Unlock()
-
 	return nil
e2e/framework/command.go (1)

391-399: Wrap the error for consistency with other helper methods.

Most other methods in this file wrap errors with context (e.g., DevPodProviderRename uses "devpod provider rename failed: %s"). Returning the raw error makes test failures harder to diagnose.

♻️ Proposed fix
 func (f *Framework) DevPodWorkspaceRebind(
 	ctx context.Context,
 	workspaceName,
 	newProvider string,
 	extraArgs ...string) error {
 	args := []string{"workspace", "rebind", workspaceName, newProvider}
 	args = append(args, extraArgs...)
-	_, _, err := f.ExecCommandCapture(ctx, args)
-	return err
+	_, stderr, err := f.ExecCommandCapture(ctx, args)
+	if err != nil {
+		return fmt.Errorf("devpod workspace rebind failed: %s", stderr)
+	}
+	return nil
 }

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/workspace/provider.go`:
- Around line 424-454: The status check in SwitchProvider is done before
acquiring the client lock, creating a TOCTOU race; acquire the lock first and
then re-check status while holding it. Change the order in SwitchProvider to
call client.Lock() immediately after Get(...), defer client.Unlock() only after
successful Lock(), then call client.Status(...) and validate status
(client2.StatusStopped or StatusNotFound) before modifying
workspace.Provider.Name and calling providerpkg.SaveWorkspaceConfig(workspace);
preserve the same error wrapping for Lock/Status/Save failures.
🧹 Nitpick comments (1)
cmd/provider/rename.go (1)

114-124: Consider extracting the max name length as a constant.

The magic number 32 on line 122 could be extracted to a named constant (e.g., MaxProviderNameLength) for improved maintainability and consistency with other validation constants in the codebase.

♻️ Optional refactor
+const MaxProviderNameLength = 32
+
 // Run executes the command
 func (cmd *RenameCmd) Run(cobraCmd *cobra.Command, args []string) error {
-
 	oldName := args[0]
 	newName := args[1]
 	if provider.ProviderNameRegEx.MatchString(newName) {
 		return fmt.Errorf("provider name can only include lowercase letters, numbers or dashes")
 	}
-	if len(newName) > 32 {
-		return fmt.Errorf("provider name cannot be longer than 32 characters")
+	if len(newName) > MaxProviderNameLength {
+		return fmt.Errorf("provider name cannot be longer than %d characters", MaxProviderNameLength)
 	}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@cmd/workspace/rebind.go`:
- Around line 37-66: Before calling workspace.SwitchProvider, validate that the
requested provider exists in the loaded devPodConfig: check devPodConfig for
newProviderName (e.g., lookup devPodConfig.Providers[newProviderName] or call an
existing helper like devPodConfig.FindProvider/HasProvider) and return a clear
error if it does not exist; update RebindCmd.Run to perform this check after
loading devPodConfig and before workspace.SwitchProvider so you never persist a
workspace config pointing to a non-existent provider.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@cmd/provider/rename.go`:
- Around line 123-131: The validateProviderName function currently allows empty
strings because ProviderNameRegEx doesn't catch them; add an explicit check at
the start of validateProviderName (the function named validateProviderName) to
return an error when newName == "" (e.g., "provider name cannot be empty")
before running ProviderNameRegEx.MatchString and the length check so empty names
are rejected and downstream filesystem/key issues are avoided.

In `@e2e/tests/workspace/rebind_test.go`:
- Around line 118-127: The test calls f.DevPodUp then immediately attempts
rebind, which can make the test flaky; update the test to wait until the
workspace is confirmed running before calling DevPodWorkspaceRebind. After
computing workspaceID (workspace.ToID(...)), poll the workspace status (e.g.,
via an existing helper like f.WaitForWorkspaceRunning or by repeatedly calling
f.GetWorkspaceStatus/GetWorkspace until status == "running" with a timeout) and
only then call f.DevPodWorkspaceRebind and framework.ExpectError; reference
DevPodUp, workspace.ToID, and DevPodWorkspaceRebind when adding the wait.
🧹 Nitpick comments (1)
e2e/tests/workspace/rebind_test.go (1)

29-36: Consider registering cleanup immediately to avoid resource leaks.

Cleanup only happens at the end, so any earlier failure can leave providers/workspaces behind and make later runs flaky. Prefer ginkgo.DeferCleanup (or defer) right after creation.

Also applies to: 83-89

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@e2e/tests/provider/provider.go`:
- Around line 349-358: The test calls DevPodProviderRename immediately after
DevPodUp, causing flakiness because the workspace may not yet be in
State.Running; add an explicit wait for the workspace to reach Running (using
the same helper used in provider_docker.go — e.g., the test helper that waits
for State.Running or WaitForWorkspaceState/WaitForState on f) after DevPodUp and
before DevPodProviderRename so the rename assertion always runs against a fully
Running workspace.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@cmd/provider/rename.go`:
- Around line 89-99: The function adjustDefaultProvider mutates
devPodConfig.Current().DefaultProvider to newName before calling
config.SaveConfig; if SaveConfig returns an error this leaves the in-memory
config pointing to newName and can break rollback logic—change the function so
that after a failed config.SaveConfig you restore
devPodConfig.Current().DefaultProvider back to oldName (do the restore before
returning the error), while keeping the existing logging via log.Default.Errorf
and log.Default.Infof; reference adjustDefaultProvider,
devPodConfig.Current().DefaultProvider, and config.SaveConfig to locate and
implement the rollback.

In `@e2e/tests/provider/provider.go`:
- Around line 328-333: After calling f.DevPodUp(ctx, tempDir) but before
f.DevPodSSH(...), add an explicit wait for the workspace to reach the Running
state (the same pattern used in the RENAME-5 test) to avoid flakiness; use the
existing helper that polls the workspace state (e.g., WaitForDevPodState or
whatever pattern is used elsewhere in this file) to wait until the dev pod is
"Running" and only then call f.DevPodSSH(ctx, tempDir, "echo 'hello'"), and make
sure to propagate and check the returned error with framework.ExpectNoError.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@e2e/tests/provider/provider.go`:
- Around line 361-364: The code discards the first deletion error and then
asserts a stale err (from CopyToTempDir); update the two calls to
f.DevPodProviderDelete so each return value is assigned (e.g., err :=
f.DevPodProviderDelete(...)) and immediately checked with
framework.ExpectNoError(err) for providerName and renamedProviderName
respectively, ensuring you reference f.DevPodProviderDelete, providerName,
renamedProviderName, and framework.ExpectNoError rather than reusing the old
err.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@e2e/tests/provider/provider.go`:
- Around line 449-455: The cleanup races because DevPodProviderDelete is invoked
before the workspace teardown finishes; update the cleanup sequence around
f.DevPodWorkspaceDelete(ctx, tempDir) and f.DevPodProviderDelete(ctx,
providerName) to wait for the workspace to be fully deleted before deleting the
provider—implement a short retry/poll loop (with timeout) that checks for
workspace absence (mirror the RENAME-4 deletion wait logic used elsewhere) and
only call f.DevPodProviderDelete after the workspace deletion check succeeds;
keep the existing error checks (framework.ExpectNoError) and surface timeout as
a test failure if the workspace does not disappear in time.
- Around line 338-342: After calling DevPodProviderRename, immediately check and
handle its error (ensure the framework.ExpectNoError(err) is placed right after
DevPodProviderRename) before calling DevPodStatus; then call DevPodStatus(ctx,
tempDir), capture its error and only access or log status.State after verifying
the DevPodStatus error is nil (use framework.ExpectNoError for the DevPodStatus
error) so that failures from DevPodStatus are not masked when logging.

In `@pkg/workspace/provider.go`:
- Around line 595-599: The code currently mutates workspace.Provider.Name to
newProviderName and then calls providerpkg.SaveWorkspaceConfig(workspace); if
SaveWorkspaceConfig returns an error, revert workspace.Provider.Name back to its
original value before returning the error to avoid leaving the in-memory
workspace in a mutated state. To fix, capture the original name (e.g., oldName
:= workspace.Provider.Name) before assignment, assign newProviderName, call
providerpkg.SaveWorkspaceConfig(workspace), and on any non-nil err set
workspace.Provider.Name = oldName and then return the wrapped error.
- Around line 584-599: After acquiring the workspace config client (Get) and
successfully calling client.Lock(), validate the runtime state before changing
provider: call the client's status method (e.g., client.Status(ctx) or
client.GetStatus(ctx)) and if the returned state is neither "Stopped" nor
"NotFound" return an error (ensuring the defer client.Unlock() still runs) to
prevent switching providers while the workspace is running; only then set
workspace.Provider.Name = newProviderName and call
providerpkg.SaveWorkspaceConfig(workspace).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/workspace/provider.go`:
- Around line 595-598: The error message returned when client.Status(...) fails
contains a typo ("sataus"); update the fmt.Errorf in the Status call handling
(the block that calls client.Status(ctx, client2.StatusOptions{ContainerStatus:
true})) to read "failed to get status for workspace %s: %w" and keep the same
workspace.ID and wrapped err values.

@blstjohnson
Copy link
Author

blstjohnson commented Feb 3, 2026

It looks like BaseWorkspaceClient Status on windows is not working properly, so, i receive always StatusNotFound instead of real workspace status, though in UI it is displayed correct.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/workspace/provider.go`:
- Around line 600-633: The code uses the undefined alias providerpkg (e.g.,
providerpkg.Workspace and providerpkg.SaveWorkspaceConfig) while the package is
already imported as provider; update SwitchProvider and any other symbols to use
the existing provider import: change the parameter type from
*providerpkg.Workspace to *provider.Workspace and replace
providerpkg.SaveWorkspaceConfig with provider.SaveWorkspaceConfig (and any other
providerpkg.* references) so the code builds and goimports no longer tries to
add a conflicting alias.

@skevetter skevetter marked this pull request as draft February 5, 2026 19:10
@skevetter skevetter marked this pull request as ready for review February 5, 2026 19:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/workspace/provider.go`:
- Around line 612-627: The lock is acquired with client.Lock(...) but defer
client.Unlock() is registered too late, causing a lock leak on early returns;
move the defer client.Unlock() immediately after the successful client.Lock(ctx)
call so the unlock is guaranteed even if subsequent calls (client.Status(ctx,
client2.StatusOptions{ContainerStatus: true}) or the status checks) return
early; update the block around client.Lock, client.Unlock, client.Status and the
workspace state checks (referencing client.Lock, client.Unlock, client.Status,
client2.StatusOptions and workspace.ID) so Unlock is always deferred right after
Lock succeeds.

@blstjohnson blstjohnson marked this pull request as draft February 5, 2026 19:17
@blstjohnson blstjohnson marked this pull request as ready for review February 7, 2026 12:43
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/workspace/provider.go`:
- Around line 623-627: The multi-line raw backtick error embeds newline and tabs
into the message; replace the backtick literal with a single-line formatted
error that does not include embedded newlines or tabs (e.g. use
fmt.Errorf("workspace %s is in state %s and cannot be switched: only stopped or
non-existent workspaces can be switched", workspace.ID, status)) or build the
message and strings.TrimSpace it before returning; change the return that
references workspace.ID and status (and the client2.StatusStopped/StatusNotFound
check) to use the single-line message instead.
🧹 Nitpick comments (2)
e2e/tests/provider/provider.go (2)

170-175: Inner context re-declares ctx, initialDir, and err, shadowing the outer scope.

Lines 171–174 use := to declare ctx, initialDir, and err, which shadow the identically named variables from lines 17–20. Since the ginkgo.Ordered context runs within the same outer ginkgo.Context, the outer variables are already valid and hold the same values. You could reuse the outer variables directly and drop the re-declaration, or rename the inner ones to make the scoping explicit.

Option: Remove the redundant re-declarations
 		ginkgo.Context("renaming providers", ginkgo.Label("rename"), ginkgo.Ordered, func() {
-			ctx := context.Background()
-			initialDir, err := os.Getwd()
-			if err != nil {
-				panic(err)
-			}
 
 			// RENAME-1.

This works because the outer ctx (line 17) and initialDir (line 18) are captured by the closure and hold the same values.


301-308: Docker/Podman detection is duplicated across RENAME-4 and RENAME-5.

The DOCKER_HOST / Podman sniffing block is identical in both tests. Consider extracting a small helper (e.g., addDockerProvider(f, ctx, name)) in the framework or locally in this file to reduce duplication.

Example helper
func addDockerProvider(f *framework.Framework, ctx context.Context, name string) error {
	dockerHost := os.Getenv("DOCKER_HOST")
	if dockerHost != "" && strings.Contains(dockerHost, "podman") {
		return f.DevPodProviderAdd(ctx, "docker", "--name", name, "--option=DOCKER_PATH=podman")
	}
	return f.DevPodProviderAdd(ctx, "docker", "--name", name)
}

Also applies to: 384-391

- command to rename provider
- command to rebind workspace to another provider
- pkg function to switch provider
- UI support for provider rename
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@desktop/src/lib/validation.ts`:
- Line 3: The PROVIDER_NAME_REGEX currently uses an extra backslash in the
character class which allows literal backslashes; update the regex used by
PROVIDER_NAME_REGEX to remove the escaping of the hyphen/backslash so it only
permits lowercase letters, digits and hyphen (e.g. change the pattern to one
that matches /^[a-z0-9-]+$/) ensuring client-side validation matches the
server-side Go regex in pkg/provider/parse.go.

In `@docs/pages/managing-providers/rename-provider.md`:
- Line 37: Update the rollback description to accurately reflect the
implementation: state that rollback reverts workspace configurations and
restores the default provider setting (if changed), and that the cloned provider
(newName) is deleted only if all workspaces were successfully reverted;
reference the rollback() behavior and the rebindWorkspaces flow to ensure
readers understand that rollback intentionally skips deleting the clone when
workspace rebinding fails to avoid leaving workspaces referencing a non-existent
provider.
🧹 Nitpick comments (5)
cmd/provider/rename.go (2)

112-112: Nit: prefer idiomatic short variable declaration.

var _, err = ... is unusual Go. The idiomatic form is _, err :=.

Suggested fix
-	var _, err = rebindWorkspaces(devPodConfig, workspacesTouched, oldName)
+	_, err := rebindWorkspaces(devPodConfig, workspacesTouched, oldName)

67-87: Consider threading the command context through rebindWorkspaces.

SwitchProvider is called with context.Background() (line 78), making the rename non-cancellable once rebinding starts. If a user sends an interrupt (e.g., Ctrl+C), the operation will continue until all workspaces are processed. Consider accepting and forwarding the parent context.

Suggested change
 func rebindWorkspaces(
+	ctx context.Context,
 	devPodConfig *config.Config,
 	workspacesToRebind []*provider.Workspace,
 	newName string,
 ) ([]*provider.Workspace, error) {
@@
-		err := workspace.SwitchProvider(context.Background(), devPodConfig, ws, newName)
+		err := workspace.SwitchProvider(ctx, devPodConfig, ws, newName)

This would require updating all callers (cloneAndRebindProvider, rollback, Run) to pass the context through.

desktop/src/views/Providers/AddProvider/ConfigureProviderOptionsForm.tsx (2)

273-290: Missing uniqueness validation for the provider name.

SetupProviderSourceForm validates that the name is unique against existing providers (lines 380-385 in that file), but this form does not. A user could enter an already-taken name and the error would only surface from the CLI, producing a less friendly UX.

♻️ Add a uniqueness check consistent with SetupProviderSourceForm

You can access providers from useProviders (already imported on line 37) and add a validate rule:

 <Input
   {...formMethods.register(FieldName.NAME, {
     pattern: {
       value: PROVIDER_NAME_REGEX,
       message: "Name can only contain lowercase letters, numbers and hyphens",
     },
     maxLength: {
       value: 32,
       message: "Name cannot be longer than 32 characters",
     },
+    validate: {
+      unique: (value) => {
+        if (!value || value === providerID) return true
+        return !providers?.[value] || "Name must be unique"
+      },
+    },
   })}
 />

Note: providers would need to be extracted from useProviders() in the ConfigureOptionsForm component (or passed down as a prop).


229-239: Unsafe as string cast on name.

data.name is typed as string | boolean | undefined (from TFieldValues), and newName on the onSave signature accepts string | undefined. The as string cast is unnecessary and masks the type mismatch — if the field were somehow a boolean, this would silently pass it through.

♻️ Safer alternative
-        newName: name as string,
+        newName: typeof name === "string" ? name : undefined,
desktop/src/views/Providers/AddProvider/SetupProviderSourceForm.tsx (1)

370-406: Use MAX_PROVIDER_NAME_LENGTH constant instead of hardcoding 32.

The MAX_PROVIDER_NAME_LENGTH constant exists in desktop/src/lib/validation.ts (line 6). Line 387 currently hardcodes 32 instead of using this constant. Since SetupProviderSourceForm already imports from that module, using the constant ensures both the form validation and the constant stay synchronized if the limit is ever updated.

♻️ Proposed fix
-import { PROVIDER_NAME_REGEX } from "../../../lib/validation"
+import { PROVIDER_NAME_REGEX, MAX_PROVIDER_NAME_LENGTH } from "../../../lib/validation"
-                      maxLength: { value: 32, message: "Name cannot be longer than 32 characters" },
+                      maxLength: { value: MAX_PROVIDER_NAME_LENGTH, message: `Name cannot be longer than ${MAX_PROVIDER_NAME_LENGTH} characters` },

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docs/pages/managing-providers/rename-provider.md`:
- Line 38: Fix the typo and grammar in the rollback caveat sentence: replace “If
workspace rebinding on rollback fails, cloned provider will remain, to avoid
orphanted workspaces” with a corrected version that adds the missing article and
fixes the misspelling—e.g. “If workspace rebinding on rollback fails, the cloned
provider will remain to avoid orphaned workspaces.” Ensure the updated sentence
appears in the same paragraph in rename-provider.md.

@blstjohnson
Copy link
Author

The failing test on windows is possibly due to an incorrect provider configuration. Windows CI machine uses podman, but provider does not set DOCKER_PATH=podman environment var or option. Struggled finding it, on rename failing tests

fix: provider name regex fixed

added more description in rename documentation
@blstjohnson
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants