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

  1. Construct the PluginRegistry from the exact same workspace value you pass to the pool constructor.
  2. Canonicalize both paths once (std::fs::canonicalize or the crate's checked_workspace_path) before comparing/deriving either.
  3. Check for symlinked or relative components in one of the paths; make both absolute and normalized.
  4. 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

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


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/d467d41950a97359. Report an issue: GitHub.