diff --git a/hive-c0re/src/agent_server.rs b/hive-c0re/src/agent_server.rs index 182e3413..9dcf468e 100644 --- a/hive-c0re/src/agent_server.rs +++ b/hive-c0re/src/agent_server.rs @@ -563,6 +563,31 @@ pub(crate) fn handle_send( // learning the label — runtime reparenting propagates for free. See // `docs/conventions.md::Recipient sentinels`. let resolved = crate::topology::resolve_recipient(agent, to); + // Validate that the resolved recipient is a known local agent or the + // special "operator" recipient. Without this check a typo in `to` + // silently queues a message nobody will ever read (issue #1165). + // + // Cross-hive messaging (`name@hive` qualified names) is not routed + // through the broker — use the Matrix MCP tools for that instead. + if resolved.contains('@') { + return AgentResponse::Err { + message: format!( + "send failed: cross-hive recipient `{resolved}` is not supported \ + via the broker — use Matrix MCP tools for cross-hive messaging" + ), + }; + } + if resolved != hive_sh4re::OPERATOR_RECIPIENT { + let state_root = crate::coordinator::Coordinator::agent_state_root(&resolved); + if !state_root.exists() { + return AgentResponse::Err { + message: format!( + "send failed: unknown recipient `{resolved}` \ + (no agent with that name exists on this hive)" + ), + }; + } + } match coord.broker.send(&Message { from: agent.to_owned(), to: resolved, diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 4441d0f4..cd4c0555 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -467,7 +467,26 @@ pub async fn rebuild_no_meta( priv_run("stop", name).await?; } on_step("nixos-container update"); - priv_run("update", name).await?; + let update_result = priv_run("update", name).await; + if let Err(ref update_err) = update_result { + // The update failed (e.g. nix build error). If the agent was + // running before we stopped it, try to bring it back up on the + // previous successful configuration so it doesn't stay dead. + // The start failure is logged but not promoted to an error — + // we always propagate the original update error (below). + if was_running { + tracing::warn!( + %name, + error = %update_err, + "nixos-container update failed; attempting restart on old config" + ); + on_step("nixos-container start (recovery)"); + if let Err(e) = priv_run("start", name).await { + tracing::warn!(%name, error = %e, "recovery start after failed update also failed"); + } + } + } + update_result?; if was_running { // Cold-start fallback on activation errors. // See `docs/coordinator.md::Cold-start fallback`.