Hmbown/CodeWhale · error

workspace must not be empty

Error message

workspace must not be empty

What it means

When a thread-update request includes a `workspace`, the path must not be the empty path (`PathBuf::new()` / empty OsStr). The workspace anchors the thread's file access and shell policy, so an empty workspace would be meaningless or unsafe.

Solutions

  1. Pass `None` for `workspace` to keep the current workspace.
  2. Resolve the workspace to a real absolute directory path before the call.
  3. On the client side, treat empty-string workspace as absent and omit the field.

Example fix

// before
UpdateThreadRequest { workspace: Some(PathBuf::from(ws_str)), .. } // ws_str may be ""

// after
UpdateThreadRequest {
    workspace: if ws_str.trim().is_empty() { None } else { Some(PathBuf::from(ws_str)) },
    ..
}
Defensive patterns

Strategy: validation

Validate before calling

if let Some(ws) = &req.workspace { if ws.as_os_str().is_empty() { req.workspace = None; } }

Type guard

fn valid_workspace(ws: &Option<PathBuf>) -> bool {
    ws.as_ref().map_or(true, |p| !p.as_os_str().is_empty())
}

Prevention

When it happens

Trigger: Calling update-thread with `workspace: Some(PathBuf::new())`; a JSON body with `"workspace": ""` deserialized into an empty PathBuf.

Common situations: A client strips the workspace field to "" when clearing it; a path variable never got assigned before the call; deserializing an empty string from config into `Option<PathBuf>` yields Some(empty).

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/f75c0624b12bb8d6. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/runtime_threads.rs:7897

        if let Some(model) = req.model.as_ref()
            && model.trim().is_empty()
        {
            bail!("model must not be empty");
        }
        if let Some(mode) = req.mode.as_ref()
            && mode.trim().is_empty()
        {
            bail!("mode must not be empty");
        }
        if let Some(permission_posture) = req.permission_posture.as_ref()
            && permission_posture.trim().is_empty()
        {
            bail!("permission_posture must not be empty");
        }
        if let Some(workspace) = req.workspace.as_ref()
            && workspace.as_os_str().is_empty()
        {
            bail!("workspace must not be empty");
        }

        // Source resolution reads config files. Do it off the Tokio worker,
        // then recheck the conversation identity before committing the grant.
        let shell_policy_workspace = if req.allow_shell == Some(true) || req.workspace.is_some() {
            let current = self.get_thread(id).await?;
            if req.allow_shell.unwrap_or(current.allow_shell) {
                let workspace = req.workspace.clone().unwrap_or(current.workspace);
                self.validate_shell_access_policy(&workspace, config_path, config_profile)
                    .await?;
                Some(workspace)
            } else {
                None
            }
        } else {
            None
        };
        let configured_sandbox_mode = self.read_config().sandbox_mode.clone();

View on GitHub (pinned to 73e0f67d83)