Hmbown/CodeWhale · error
Project MCP server cwd must stay within workspace: {}
Error message
Project MCP server cwd must stay within workspace: {} What it means
Servers declared in a workspace's .codewhale/mcp.json get their cwd resolved like plugin cwds: relative cwds are joined to the workspace, the result is canonicalized, and it must remain inside the (canonicalized) workspace root. A cwd that escapes via absolute path, '..', or a symlink is rejected so a project config cannot launch processes outside the project.
Source
Thrown at crates/tui/src/mcp.rs:3883
} else {
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(workspace)
};
normalize_path_components(&absolute)
}
fn resolve_project_mcp_cwd(workspace: &Path, cwd: Option<&Path>) -> Result<PathBuf> {
let cwd = match cwd {
Some(cwd) if cwd.is_relative() => normalize_path_components(&workspace.join(cwd)),
Some(cwd) => normalize_path_components(cwd),
None => workspace.to_path_buf(),
};
let resolved = cwd
.canonicalize()
.unwrap_or_else(|_| normalize_path_components(&cwd));
if !resolved.starts_with(workspace) {
anyhow::bail!(
"Project MCP server cwd must stay within workspace: {}",
resolved.display()
);
}
Ok(resolved)
}
fn normalize_path_components(path: &Path) -> PathBuf {
let mut normalized = PathBuf::new();
for component in path.components() {
match component {
Component::Prefix(_) | Component::RootDir => {
normalized.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
normalized.pop();
}View on GitHub (pinned to 8880682c63)
Solutions
- Keep the server process cwd inside the workspace ('.' or a subdirectory)
- If the server must run elsewhere, declare it in the user-level ~/.codewhale/mcp.json instead of the project config
- Remove symlink hops inside the workspace so canonicalized paths stay under the workspace root
Example fix
// before (.codewhale/mcp.json)
"build-server": { "command": "./watch.sh", "cwd": "../shared-scripts" }
// after
"build-server": { "command": "./watch.sh", "cwd": "." } Defensive patterns
Strategy: validation
Validate before calling
// Before starting a project MCP server, verify its cwd stays in the workspace: let resolved = declared_cwd.canonicalize().unwrap_or_else(|_| normalize_path_components(&declared_cwd)); anyhow::ensure!(resolved.starts_with(&canonical_workspace), "project MCP cwd escapes workspace");
Type guard
fn project_cwd_within_workspace(cwd: &std::path::Path, workspace: &std::path::Path) -> bool {
cwd.canonicalize()
.map(|c| c.starts_with(workspace))
.unwrap_or_else(|_| normalize_path_components(cwd).starts_with(workspace))
} Prevention
- Write project .codewhale/mcp.json with cwd '.' or in-repo subdirectories only
- Put servers that must run outside the workspace into the user-level config instead
- Avoid symlinks inside the workspace that resolve outside it - canonicalization will trip the guard
When it happens
Trigger: A project .codewhale/mcp.json server entry with cwd set to an absolute path outside the workspace, a relative path containing '..', or a directory that is a symlink pointing outside the workspace.
Common situations: A repo whose MCP server expects to run from a monorepo sibling directory; the workspace path itself containing a symlink so canonicalization changes the prefix; moving the repo so a previously-inner path resolves elsewhere.
Related errors
- reviewed plugin stdio cwd escaped its staged root
- reviewed plugin MCP path escaped its staged root
- MCP config path cannot contain '..' components
- reviewed plugin stage could not be opened for launch
- Refusing to {operation} MCP server '{server_name}' from plug
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/f2a01228220a6742.
Report an issue: GitHub.