From 772fdd8320af79b7813a19e93172d7ce9edeef51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Sat, 16 May 2026 17:24:04 +0200 Subject: [PATCH] forward plugin install failures to manager from sub-agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install_configured now takes an optional notify recipient. on a non-zero or spawn-failed 'claude plugin install', sub-agents send the spec + stderr to manager via the hyperhive socket; manager passes None so it doesn't message itself. boot still proceeds either way — notification is best-effort. --- hive-ag3nt/src/bin/hive-ag3nt.rs | 2 +- hive-ag3nt/src/bin/hive-m1nd.rs | 2 +- hive-ag3nt/src/plugins.rs | 47 ++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/hive-ag3nt/src/bin/hive-ag3nt.rs b/hive-ag3nt/src/bin/hive-ag3nt.rs index 2be2dde..d486e80 100644 --- a/hive-ag3nt/src/bin/hive-ag3nt.rs +++ b/hive-ag3nt/src/bin/hive-ag3nt.rs @@ -71,7 +71,7 @@ async fn main() -> Result<()> { let login_state = Arc::new(Mutex::new(initial)); let bus = Bus::new(); let files = turn::TurnFiles::prepare(&cli.socket, &label, mcp::Flavor::Agent).await?; - plugins::install_configured().await; + plugins::install_configured(&cli.socket, Some("manager")).await; tokio::spawn(web_ui::serve( label, port, diff --git a/hive-ag3nt/src/bin/hive-m1nd.rs b/hive-ag3nt/src/bin/hive-m1nd.rs index aa70079..f0b0cc0 100644 --- a/hive-ag3nt/src/bin/hive-m1nd.rs +++ b/hive-ag3nt/src/bin/hive-m1nd.rs @@ -61,7 +61,7 @@ async fn main() -> Result<()> { let login_state = Arc::new(Mutex::new(initial)); let bus = Bus::new(); let files = turn::TurnFiles::prepare(&cli.socket, &label, mcp::Flavor::Manager).await?; - plugins::install_configured().await; + plugins::install_configured(&cli.socket, None).await; tokio::spawn(web_ui::serve( label, port, diff --git a/hive-ag3nt/src/plugins.rs b/hive-ag3nt/src/plugins.rs index 70d2045..f1302d0 100644 --- a/hive-ag3nt/src/plugins.rs +++ b/hive-ag3nt/src/plugins.rs @@ -7,11 +7,21 @@ //! recreate is fine. Failures log a warning but do not abort boot — //! we'd rather start without a plugin than refuse to serve. +use std::path::Path; + use tokio::process::Command; +use crate::client; + const PLUGINS_PATH: &str = "/etc/hyperhive/claude-plugins.json"; -pub async fn install_configured() { +/// Install every plugin in `/etc/hyperhive/claude-plugins.json`. When +/// `notify_recipient` is `Some(name)`, install failures also get sent +/// as a hyperhive message to that recipient (typically `"manager"` for +/// sub-agents) so it surfaces in the inbox rather than being buried in +/// journald. The manager itself passes `None` — there's nobody above +/// it to notify. +pub async fn install_configured(socket: &Path, notify_recipient: Option<&str>) { let raw = match tokio::fs::read_to_string(PLUGINS_PATH).await { Ok(s) => s, Err(_) => return, @@ -33,16 +43,49 @@ pub async fn install_configured() { tracing::info!(spec = %spec, "claude plugin install ok"); } Ok(out) => { + let stderr = String::from_utf8_lossy(&out.stderr).into_owned(); tracing::warn!( spec = %spec, status = ?out.status, - stderr = %String::from_utf8_lossy(&out.stderr), + stderr = %stderr, "claude plugin install failed", ); + if let Some(to) = notify_recipient { + notify( + socket, + to, + format!( + "claude plugin install failed for `{spec}`:\n{}", + stderr.trim() + ), + ) + .await; + } } Err(e) => { tracing::warn!(spec = %spec, error = ?e, "claude plugin install spawn failed"); + if let Some(to) = notify_recipient { + notify( + socket, + to, + format!("claude plugin install spawn failed for `{spec}`: {e}"), + ) + .await; + } } } } } + +/// Best-effort hyperhive send. Swallows transport errors — the warn log +/// is already in journald and the harness boot must not stall waiting +/// for the broker to be reachable. +async fn notify(socket: &Path, to: &str, body: String) { + let req = hive_sh4re::AgentRequest::Send { + to: to.to_owned(), + body, + }; + if let Err(e) = client::request::<_, hive_sh4re::AgentResponse>(socket, &req).await { + tracing::warn!(error = ?e, "failed to notify {to} of plugin install failure"); + } +}