From cff3b6ff85e8e89adf2c963b585a3bf10a512c5d Mon Sep 17 00:00:00 2001 From: damocles Date: Wed, 3 Jun 2026 21:39:57 +0200 Subject: [PATCH] fix(#1201): prevent sync_agents from dropping all agents on list() failure --- hive-c0re/src/lifecycle.rs | 9 ++++++++- hive-c0re/src/meta.rs | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 7340a7db..62a35721 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -276,8 +276,15 @@ pub async fn spawn( /// where the new agent's container doesn't exist yet). Pass empty /// `name_to_add` from rebuild paths where the agent is already in the /// container list. +/// +/// Propagates errors from `list()` rather than swallowing them. +/// Using `.unwrap_or_default()` here would silently produce an empty +/// agent list when `nixos-container list` fails (priv helper down, race), +/// which `sync_agents` would then commit to meta — dropping every agent +/// from `flake.nix`. Callers that can tolerate failures (e.g. migration) +/// handle the `Err` themselves with `.unwrap_or_default()`. async fn agents_for_meta(name_to_add: Option<&str>) -> Result> { - let containers = list().await.unwrap_or_default(); + let containers = list().await?; let mut out: Vec = containers .into_iter() .filter_map(|c| { diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 32eea594..9c05baf9 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -80,6 +80,21 @@ pub async fn sync_agents( return Ok(()); } + // Safety guard: refuse to write an empty agent list over a non-empty + // on-disk flake. An empty `agents` slice is never intentional — it + // means `nixos-container list` failed and the caller got an empty + // fallback. Overwriting here would drop every agent from the meta + // flake and trigger unnecessary (and potentially destructive) cascade + // rebuilds. Callers that genuinely need to clear the agent list + // (there are none today) must handle this case explicitly. + if agents.is_empty() && !initial && !on_disk.is_empty() { + tracing::warn!( + "sync_agents: refusing to overwrite non-empty meta flake with empty agent list \ + (nixos-container list may have failed)" + ); + return Ok(()); + } + std::fs::write(&flake_path, &new_flake) .with_context(|| format!("write {}", flake_path.display()))?;