From 1f0cc010adb05b12bf9a4053d58af871ae9e7f06 Mon Sep 17 00:00:00 2001 From: damocles Date: Thu, 25 Jun 2026 00:36:52 +0200 Subject: [PATCH] fix(#1989): don't relock on meta-update cascade rebuilds (was reverting the bump) --- hive-c0re/src/auto_update.rs | 11 ++++++++++- hive-c0re/src/lifecycle.rs | 12 +++++++++++- hive-c0re/src/rebuild_queue.rs | 17 ++++++++++++++--- hive-c0re/src/server.rs | 2 +- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index a265ddce..fe0f60ab 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -67,11 +67,17 @@ pub fn agent_config_pending(name: &str, deployed_sha: Option<&str>) -> bool { /// the `rebuild_queue` worker (lets the function annotate its phase via /// `coord.set_queue_step`) and `None` when called directly (e.g. the /// root-agent migration nudge in `ensure_root_agent`). +/// +/// `relock` bumps the agent's meta input to `applied//main` before +/// the container rebuild. Pass `false` only for meta-update cascade +/// rebuilds, where re-locking would revert the bump the cascade just +/// committed (see `lifecycle::rebuild`). pub async fn rebuild_agent( coord: &Arc, name: &str, current_rev: &str, queue_entry_id: Option, + relock: bool, ) -> Result<()> { tracing::info!(%name, rev = %current_rev, "rebuild agent"); let agent_dir = coord @@ -88,6 +94,7 @@ pub async fn rebuild_agent( name, &hive, &paths, + relock, &|step| coord.set_queue_step(queue_entry_id, step), &|log_id| { if let Some(qid) = queue_entry_id @@ -176,7 +183,9 @@ pub async fn ensure_root_agent(coord: &Arc) -> Result<()> { "manager container exists but no applied flake — forcing rebuild to migrate" ); let coord_clone = coord.clone(); - if let Err(e) = rebuild_agent(&coord_clone, MANAGER_NAME, rev.as_str(), None).await { + if let Err(e) = + rebuild_agent(&coord_clone, MANAGER_NAME, rev.as_str(), None, true).await + { tracing::warn!(error = ?e, "manager migration rebuild failed"); } } else { diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 1ea2a5f5..077c0879 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -413,6 +413,7 @@ pub async fn rebuild( name: &str, hive: &HiveEnv, paths: &AgentPaths, + relock: bool, on_step: &(dyn Fn(&str) + Send + Sync), on_build_log_id: &(dyn Fn(i64) + Send + Sync), ) -> Result<()> { @@ -426,7 +427,16 @@ pub async fn rebuild( // Then bump just this agent's input — picks up whatever // `applied//main` currently points at (deployed/). // Commits the lock if it changed. - crate::meta::lock_update_for_rebuild(name).await?; + // + // `relock = false` skips this: a meta-update cascade has *just* set + // the meta lock deliberately, and `lock_update_for_rebuild` re-runs + // `nix flake update agent-`, which re-resolves the agent's + // transitive inputs back to the agent's own flake.lock — reverting + // the input the meta-update just bumped. Cascade rebuilds therefore + // build against the freshly-set on-disk lock as-is. + if relock { + crate::meta::lock_update_for_rebuild(name).await?; + } rebuild_no_meta(name, hive, paths, on_step, on_build_log_id).await } diff --git a/hive-c0re/src/rebuild_queue.rs b/hive-c0re/src/rebuild_queue.rs index 55bd76f5..e40cad20 100644 --- a/hive-c0re/src/rebuild_queue.rs +++ b/hive-c0re/src/rebuild_queue.rs @@ -886,8 +886,19 @@ async fn dispatch( (QueueKind::Rebuild, None) => { let current_rev = crate::auto_update::current_flake_rev(&coord.hyperhive_flake).unwrap_or_default(); - crate::auto_update::rebuild_agent(coord, &entry.agent, ¤t_rev, Some(entry.id)) - .await + // A meta-update cascade has just set the meta lock; re-locking + // in the per-agent rebuild would revert it (the agent's own + // flake.lock wins). Every other source wants the relock so it + // advances to applied//main. + let relock = entry.source != QueueSource::MetaUpdate; + crate::auto_update::rebuild_agent( + coord, + &entry.agent, + ¤t_rev, + Some(entry.id), + relock, + ) + .await } (QueueKind::MetaUpdate, Some(approval_id)) => { crate::actions::run_approval_update_meta_inputs(coord, Some(entry.id), approval_id) @@ -987,7 +998,7 @@ async fn dispatch( // env var takes effect in the container. let current_rev = crate::auto_update::current_flake_rev(&coord.hyperhive_flake).unwrap_or_default(); - crate::auto_update::rebuild_agent(coord, name, ¤t_rev, Some(entry.id)).await + crate::auto_update::rebuild_agent(coord, name, ¤t_rev, Some(entry.id), true).await } (QueueKind::GracefulStop, _) => run_graceful_stop(coord, entry).await, (QueueKind::Start, _) => run_start(coord, entry).await, diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index 0f69098e..13ca9719 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -425,7 +425,7 @@ async fn handle_rebuild(coord: &Arc, name: &str) -> Result