Hmbown/CodeWhale · error · anyhow::Error
plugin registry workspace does not match MCP pool workspace
Error message
plugin registry workspace does not match MCP pool workspace
What it means
McpPool::from_config_path_with_workspace_and_plugins requires that the PluginRegistry it is given was constructed for exactly the same workspace path as the pool's workspace. The check plugins.workspace() != workspace is a pure path-equality precondition violation — the config file is not even read before it fails. It exists to prevent a pool and its plugin registry from disagreeing about which workspace's reviewed plugins are trusted.
Source
Thrown at crates/tui/src/mcp.rs:2422
/// Create a pool from global MCP config plus workspace-local
/// `.codewhale/mcp.json`. Project servers override same-name global
/// servers and default stdio `cwd` to the workspace root.
#[cfg(test)]
pub fn from_config_path_with_workspace(
path: &std::path::Path,
workspace: &Path,
) -> Result<Self> {
let plugins = Arc::new(crate::plugins::PluginRegistry::empty(workspace));
Self::from_config_path_with_workspace_and_plugins(path, workspace, plugins)
}
pub fn from_config_path_with_workspace_and_plugins(
path: &std::path::Path,
workspace: &Path,
plugins: Arc<crate::plugins::PluginRegistry>,
) -> Result<Self> {
if plugins.workspace() != workspace {
anyhow::bail!("plugin registry workspace does not match MCP pool workspace");
}
let config = load_config_with_workspace_and_plugins(path, workspace, plugins.as_ref())?;
let workspace = checked_workspace_path(workspace)?;
let mut pool = Self::new(config);
pool.config_sources = vec![
path.to_path_buf(),
checked_workspace_mcp_config_path(&workspace)?,
];
pool.config_sources
.extend(crate::config::workspace_trust_config_candidate_paths());
pool.last_mtimes = pool
.config_sources
.iter()
.map(|source| mcp_config_mtime(source))
.collect();
pool.workspace = Some(workspace);
pool.plugin_registry = Some(plugins);
Ok(pool)View on GitHub (pinned to 8880682c63)
Solutions
- Construct the PluginRegistry from the exact same workspace value you pass to the pool constructor.
- Canonicalize both paths once (std::fs::canonicalize or the crate's checked_workspace_path) before comparing/deriving either.
- Check for symlinked or relative components in one of the paths; make both absolute and normalized.
- If the mismatch is intentional, rethink the design — the API deliberately forbids split workspaces.
Example fix
// before
let plugins = Arc::new(PluginRegistry::empty(Path::new("/repo")));
let pool = McpPool::from_config_path_with_workspace_and_plugins(
&cfg, Path::new("/repo/symlink-target"), plugins)?; // bail: workspace mismatch
// after
let workspace = std::fs::canonicalize("/repo")?;
let plugins = Arc::new(PluginRegistry::empty(workspace.clone()));
let pool = McpPool::from_config_path_with_workspace_and_plugins(
&cfg, &workspace, plugins)?; Defensive patterns
Strategy: validation
Validate before calling
// Rust: assert precondition before constructing the pool
fn assert_same_workspace(plugins: &PluginRegistry, ws: &Path) -> Result<()> {
if plugins.workspace() != ws {
bail!("registry workspace {:?} != pool workspace {:?}", plugins.workspace(), ws);
}
Ok(())
}
assert_same_workspace(&plugins, &workspace)?;
let pool = McpPool::from_config_path_with_workspace_and_plugins(&path, &workspace, plugins)?; Prevention
- Create the PluginRegistry and the pool from one shared, canonicalized workspace value.
- Canonicalize with the same helper (checked_workspace_path) on both sides so symlinks can't split them.
- Unit-test constructor pairs with mismatched paths to fail fast at the call site.
When it happens
Trigger: Calling from_config_path_with_workspace_and_plugins with a PluginRegistry built via PluginRegistry::empty(dir_a) (or loaded from dir_a) while passing dir_b as the workspace, including cases where the two paths differ only by symlink resolution, trailing components, or relative-vs-absolute spelling.
Common situations: Refactors where the registry is created early from a config-derived path and the pool later from a canonicalized workspace root; tests building fixtures in temp dirs and passing mismatched paths; symlinked checkouts where one caller canonicalizes and the other does not.
Related errors
- workspace path cannot be empty
- workspace path cannot contain '..' components
- ${name} is unavailable in Workflow scripts: runs must be det
- new Date()/Date() is unavailable in Workflow scripts: runs m
- task(): expected an options object
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/d467d41950a97359.
Report an issue: GitHub.