{"record":{"id":"57546bba273a2b00","repo":"Hmbown/CodeWhale","slug":"mcp-pool-has-no-configuration-source-to-reload","errorCode":null,"errorMessage":"MCP pool has no configuration source to reload","messagePattern":"MCP pool has no configuration source to reload","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/tui/src/mcp.rs","lineNumber":2522,"sourceCode":"    }\n\n    /// If the source config file's mtime has changed since the last check,\n    /// re-read it and (only when the content hash also changed) drop all\n    /// existing connections so the next `get_or_connect` reattaches under\n    /// the new config. No-op when the pool was constructed via [`McpPool::new`]\n    /// (no source path), when stat fails, or when the file content is\n    /// byte-identical to what we last loaded. Returns `Ok(true)` if any\n    /// connections were dropped, `Ok(false)` otherwise.\n    ///\n    /// This is the lazy half of the auto-reload story for #1267: instead of a\n    /// long-lived file watcher, the next tool invocation pays a single `stat`\n    /// call (and only re-reads the file when the mtime moved). On networked\n    /// or remote filesystems where mtime granularity is poor, the hash\n    /// compare keeps us from churning connections on every check.\n    fn reload_from_config_sources(&mut self, force: bool) -> Result<bool> {\n        if self.config_sources.is_empty() {\n            if force {\n                anyhow::bail!(\"MCP pool has no configuration source to reload\");\n            }\n            return Ok(false);\n        }\n        let current_mtimes: Vec<_> = self\n            .config_sources\n            .iter()\n            .map(|path| mcp_config_mtime(path))\n            .collect();\n        if !force && current_mtimes == self.last_mtimes {\n            return Ok(false);\n        }\n        // An mtime moved, or the user explicitly requested a reload: re-read\n        // the complete global + workspace + plugin-backed config.\n        let primary = self\n            .config_sources\n            .first()\n            .context(\"MCP config source list unexpectedly empty\")?;\n        let new_config = if let Some(workspace) = self.workspace.as_deref() {","sourceCodeStart":2504,"sourceCodeEnd":2540,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp.rs#L2504-L2540","documentation":"reload_from_config_sources(force=true) implements /mcp reload and startup reconnects: it stat's each recorded config source and re-reads when mtimes moved. If the pool has no recorded sources (config_sources empty — an ad-hoc pool built via McpPool::new or a default config with no files) and a forced reload is requested, it bails with this error instead of pretending to reload nothing. With force=false it just returns Ok(false).","triggerScenarios":"Calling the forced-reload entry point (user runs /mcp reload, or a caller passes force=true) on a pool that was constructed directly from an McpConfig value rather than from a config file path, so no source paths were ever recorded.","commonSituations":"Embedding the pool with a programmatic config in tests or tools and then triggering the reload command; a session that started with a completely absent config file where even fallback sources resolved to nothing; callers assuming reload always has a file to re-read.","solutions":["Construct the pool through from_config_path... / empty_with_workspace_config_sources so config_sources records the real file paths.","If using a purely programmatic config, don't expose or invoke forced reload — replace the config via the switch/transactional APIs instead.","Create the expected config file on disk so the reload has a source.","Guard the UI: disable /mcp reload when no sources are recorded."],"exampleFix":"// before\nlet mut pool = McpPool::new(McpConfig::default());\n// later: /mcp reload -> 'MCP pool has no configuration source to reload'\n\n// after\nlet mut pool = McpPool::from_config_path_with_workspace(&cfg_path, &workspace)?;\n// reload now re-reads cfg_path and the workspace/trust candidates","handlingStrategy":"validation","validationCode":"// Rust: only offer forced reload when sources exist\nfn can_force_reload(pool: &McpPool) -> bool {\n    !pool.config_sources().is_empty() // expose or inspect recorded sources\n}\nif can_force_reload(&pool) {\n    pool.reload_from_config_sources(true)?;\n} else {\n    tracing::info!(\"no MCP config source recorded; nothing to reload\");\n}","typeGuard":null,"tryCatchPattern":"// Rust: degrade gracefully when there is nothing to reload\nmatch pool.reload_from_config_sources(true) {\n    Err(e) if e.to_string().contains(\"no configuration source to reload\") => {\n        // informational: programmatic pool, skip the reload action\n    }\n    other => other?,\n}","preventionTips":["Build pools from a config file path so reload always has a source.","In UIs, disable the reload command for programmatic/source-less pools.","Remember force=false on an empty pool is a no-op Ok(false), not an error."],"tags":["mcp","config","reload","state"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}