charmbracelet/crush · error
failed to resolve workspace path: %w
Error message
failed to resolve workspace path: %w
What it means
CreateWorkspace resolves the caller-supplied workspace path via resolveWorkspaceKey before registering it in the backend's dedupe map. Any failure resolving (e.g. empty path, evaluation/symlink/stat failure) is wrapped with this message and aborts workspace creation.
Source
Thrown at internal/backend/backend.go:356
// parameters, or returns an existing workspace if one already exists at
// the same resolved path (first-wins semantics).
//
// args.ClientID must be a valid UUID identifying the calling client;
// the resulting workspace registers a creation hold on behalf of that
// client which is released either by the first SSE attach (which
// converts it into a stream claim) or by the grace window expiring.
func (b *Backend) CreateWorkspace(args proto.Workspace) (*Workspace, proto.Workspace, error) {
if args.Path == "" {
return nil, proto.Workspace{}, ErrPathRequired
}
clientID, err := validateClientID(args.ClientID)
if err != nil {
return nil, proto.Workspace{}, err
}
key, err := resolveWorkspaceKey(args.Path)
if err != nil {
return nil, proto.Workspace{}, fmt.Errorf("failed to resolve workspace path: %w", err)
}
b.mu.Lock()
if err := b.admitLocked(clientID); err != nil {
b.mu.Unlock()
return nil, proto.Workspace{}, err
}
// A client is arriving: cancel any pending idle shutdown so we never
// hand back a workspace on a server that is about to tear itself down.
b.cancelShutdownLocked()
if existingID, ok := b.pathIndex[key]; ok {
if ws, found := b.workspaces.Get(existingID); found {
// Hold b.mu while registering: teardown also
// acquires b.mu before tearing the workspace
// down, so this guarantees the workspace we
// return cannot be torn out from under us
// between lookup and registerClient. Lock order
// here is b.mu -> ws.clientsMu.View on GitHub (pinned to 7944b8e522)
Solutions
- Pass an existing, absolute directory path as args.Path
- Create the directory before calling CreateWorkspace
- Fix broken symlinks or mount the path into the environment
Example fix
// before
b.CreateWorkspace(ctx, proto.CreateWorkspaceArgs{Path: ""})
// after
abs, _ := filepath.Abs("/home/user/project")
os.MkdirAll(abs, 0o755)
b.CreateWorkspace(ctx, proto.CreateWorkspaceArgs{Path: abs}) Defensive patterns
Strategy: validation
Validate before calling
func ensureWorkspacePath(p string) error {
if p == "" {
return errors.New("workspace path is empty")
}
abs, err := filepath.Abs(p)
if err != nil {
return err
}
fi, err := os.Stat(abs)
if err != nil {
return fmt.Errorf("workspace path %q: %w", abs, err)
}
if !fi.IsDir() {
return fmt.Errorf("%q is not a directory", abs)
}
return nil
} Try / catch
ws, err := b.CreateWorkspace(ctx, args)
if err != nil && strings.Contains(err.Error(), "failed to resolve workspace path") {
return fmt.Errorf("cannot open workspace: %w", err)
} Prevention
- Pass absolute, existing directory paths to CreateWorkspace
- Stat/symlink-resolve the path client-side before calling
- Create the project directory before opening a workspace on it
When it happens
Trigger: Calling Backend.CreateWorkspace with args.Path that is empty, does not exist, cannot be made absolute, or fails symlink/stat resolution.
Common situations: Passing a relative path from a different working directory; pointing at a deleted directory; symlink to a missing target; running the client from a container with a non-mounted path.
Related errors
- path does not exist: %s
- client not attached
- workspace closing
- requested channels differ from the existing workspace; chann
- not found
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/0ea3c775c4a251bd.
Report an issue: GitHub.