diff --git a/hive-agent/src/events.rs b/hive-agent/src/events.rs index 1d2538ec..f0393774 100644 --- a/hive-agent/src/events.rs +++ b/hive-agent/src/events.rs @@ -677,9 +677,18 @@ impl Bus { /// never touches the session. It exists because the harness is /// otherwise blind to the init event — the only place claude reports /// MCP-server status — so a dropped/failed stdio bridge (e.g. matrix - /// after a core bounce) went silent. Emitted every degraded turn on - /// purpose: whether it persists across fresh turns is the signal that - /// picks the real fix. + /// after a core bounce) went silent. + /// + /// `pending` is deliberately NOT treated as degraded: it's the claude + /// CLI's normal init-event race for a freshly spawned stdio server + /// (status is reported before the handshake completes, then flips to + /// `connected` moments later in the same turn — see the upstream + /// claude-agent-sdk-typescript repo, issue number 368, for confirmation). + /// Flagging it fired on nearly every turn for all three configured + /// servers with zero actual impact (confirmed: tools kept working), + /// which is why the MCP-dropped-after-restart tracker issue's + /// persistent-vs-one-turn repro question looked answered "persistent" + /// when it wasn't — that was this false positive, not the real bug. pub(crate) fn observe_mcp_health(&self, v: &serde_json::Value) { if v.get("type").and_then(|t| t.as_str()) != Some("system") || v.get("subtype").and_then(|s| s.as_str()) != Some("init") @@ -886,8 +895,11 @@ impl Default for Bus { /// Compare the configured MCP server names against the `{name -> status}` /// map parsed from the init event, returning display strings for the /// degraded ones: a configured server absent from the report was dropped -/// entirely; one present with a non-`connected` status failed to register. -/// Pure so it's unit-testable without a live `Bus`. +/// entirely; one present with a non-`connected`, non-`pending` status +/// failed to register. `pending` is excluded — it's the CLI's normal +/// init-event race for a server still completing its handshake, not a +/// failure (see the doc comment on `observe_mcp_health`). Pure so it's +/// unit-testable without a live `Bus`. fn degraded_mcp_servers( configured: &[String], reported: &std::collections::HashMap<&str, &str>, @@ -895,7 +907,7 @@ fn degraded_mcp_servers( configured .iter() .filter_map(|name| match reported.get(name.as_str()).copied() { - Some("connected") => None, + Some("connected" | "pending") => None, Some(status) => Some(format!("{name} ({status})")), None => Some(format!("{name} (absent)")), }) @@ -963,4 +975,18 @@ mod tests { .collect(); assert!(super::degraded_mcp_servers(&configured, &reported).is_empty()); } + + #[test] + fn degraded_mcp_servers_ignores_pending() { + // `pending` is the CLI's normal init-event race for a stdio server + // still completing its handshake (flips to `connected` moments + // later in the same turn) — not a real failure, per the + // MCP-dropped-after-restart tracker issue. + use std::collections::HashMap; + let configured = ["hyperhive".to_owned(), "matrix".to_owned()]; + let reported: HashMap<&str, &str> = [("hyperhive", "pending"), ("matrix", "pending")] + .into_iter() + .collect(); + assert!(super::degraded_mcp_servers(&configured, &reported).is_empty()); + } }