fix: fall back to name lookup for UUID-shaped workspace names (#24340)

`namedWorkspace` in `cli/root.go` parsed workspace identifiers with
`uuid.Parse` first and returned immediately on success, even when no
workspace had that UUID as its actual ID. This caused 404 errors for any
workspace whose name was a valid 32-char hex string (dashless UUID).

- Add `codersdk.ResolveWorkspace`: tries UUID lookup first, falls back
to name lookup on 404. `NameValid` guard skips the fallback for standard
dashed UUIDs (36 chars > 32-char name limit).
- Export `codersdk.SplitWorkspaceIdentifier`, replacing the duplicate
`splitNamedWorkspace` in `cli/root.go` (uses `strings.Cut`).
- Delete `namedWorkspace` from `cli/root.go`; all 28 call sites now use
`client.ResolveWorkspace` directly.
- Delete `namedWorkspace` and `splitNameAndOwner` from
`codersdk/toolsdk/bash.go`; inline `client.ResolveWorkspace`.
- Simplify `GetWorkspace` tool handler to a single `ResolveWorkspace`
call.
- Unit tests via httptest mock cover UUID, name, owner/name, UUID-like
fallback, not-found, server error, transport error, and invalid
identifier paths.
- Integration tests in `cli/show_test.go` and `codersdk/toolsdk` for
workspaces with UUID-like names.

> Generated with Coder Agents
This commit is contained in:
Cian Johnston
2026-04-27 12:58:26 +01:00
committed by GitHub
parent 23b30b7285
commit d5a5be116d
25 changed files with 498 additions and 102 deletions
-31
View File
@@ -27,7 +27,6 @@ import (
"text/tabwriter"
"time"
"github.com/google/uuid"
"github.com/mattn/go-isatty"
"github.com/mitchellh/go-wordwrap"
"golang.org/x/mod/semver"
@@ -1071,36 +1070,6 @@ func (o *OrganizationContext) Selected(inv *serpent.Invocation, client *codersdk
return codersdk.Organization{}, xerrors.Errorf("Must select an organization with --org=<org_name>. Choose from: %s", strings.Join(validOrgs, ", "))
}
func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) {
parts := strings.Split(identifier, "/")
switch len(parts) {
case 1:
owner = codersdk.Me
workspaceName = parts[0]
case 2:
owner = parts[0]
workspaceName = parts[1]
default:
return "", "", xerrors.Errorf("invalid workspace name: %q", identifier)
}
return owner, workspaceName, nil
}
// namedWorkspace fetches and returns a workspace by an identifier, which may be either
// a bare name (for a workspace owned by the current user) or a "user/workspace" combination,
// where user is either a username or UUID.
func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier string) (codersdk.Workspace, error) {
if uid, err := uuid.Parse(identifier); err == nil {
return client.Workspace(ctx, uid)
}
owner, name, err := splitNamedWorkspace(identifier)
if err != nil {
return codersdk.Workspace{}, err
}
return client.WorkspaceByOwnerAndName(ctx, owner, name, codersdk.WorkspaceOptions{})
}
func initAppearance(ctx context.Context, client *codersdk.Client) codersdk.AppearanceConfig {
// best effort
cfg, _ := client.Appearance(ctx)