Hmbown/CodeWhale · error

MCP pool has no configuration source to reload

Error message

MCP pool has no configuration source to reload

What it means

`reload_from_config_sources(force = true)` was called but the pool has no recorded configuration sources, so there is nothing to reload. The non-forced path silently returns `Ok(false)`; only an explicit forced reload errors, because the caller demanded a reload that cannot happen.

Solutions

  1. Construct the pool via `from_config_path*` so `config_sources` is populated.
  2. Only call the forced reload when `config_sources` is non-empty; use the non-forced check otherwise.
  3. If the pool is intentionally file-less, remove it from the reload loop instead of force-reloading.

Example fix

// before
pool.reload_from_config_sources(true)?; // panics/errors when no sources
// after
if pool.has_config_sources() {
    pool.reload_from_config_sources(true)?;
}
Defensive patterns

Strategy: type-guard

Validate before calling

if pool.config_sources.is_empty() {
    // nothing to reload; skip instead of forcing
    return Ok(false);
}

Type guard

fn reloadable(pool: &McpPool) -> bool { !pool.config_sources.is_empty() }

Try / catch

match pool.reload_from_config_sources(force) {
    Err(e) if e.to_string().contains("no configuration source to reload") => Ok(false),
    other => other,
}

Prevention

When it happens

Trigger: Calling a forced config reload (hot-reload / manual reload API) on an `McpPool` built without `config_sources` (e.g. via `McpConfig::default()` without `from_config_path*`).

Common situations: Programmatically constructed pools (no config file) hooked into a file-watcher reload loop; calling force-reload after sources were cleared.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/mcp.rs:3028

    }

    /// If the source config file's mtime has changed since the last check,
    /// re-read it and (only when the content hash also changed) drop all
    /// existing connections so the next `get_or_connect` reattaches under
    /// the new config. No-op when the pool was constructed via [`McpPool::new`]
    /// (no source path), when stat fails, or when the file content is
    /// byte-identical to what we last loaded. Returns `Ok(true)` if any
    /// connections were dropped, `Ok(false)` otherwise.
    ///
    /// This is the lazy half of the auto-reload story for #1267: instead of a
    /// long-lived file watcher, the next tool invocation pays a single `stat`
    /// call (and only re-reads the file when the mtime moved). On networked
    /// or remote filesystems where mtime granularity is poor, the hash
    /// compare keeps us from churning connections on every check.
    fn reload_from_config_sources(&mut self, force: bool) -> Result<bool> {
        if self.config_sources.is_empty() {
            if force {
                anyhow::bail!("MCP pool has no configuration source to reload");
            }
            return Ok(false);
        }
        let current_mtimes: Vec<_> = self
            .config_sources
            .iter()
            .map(|path| mcp_config_mtime(path))
            .collect();
        if !force && current_mtimes == self.last_mtimes {
            return Ok(false);
        }
        // An mtime moved, or the user explicitly requested a reload: re-read
        // the complete global + workspace + plugin-backed config.
        let primary = self
            .config_sources
            .first()
            .context("MCP config source list unexpectedly empty")?;
        let new_config = if let Some(workspace) = self.workspace.as_deref() {

View on GitHub (pinned to 73e0f67d83)