From 4be2279485fc76ab604f5ce1d52bd995eb0f3b6b Mon Sep 17 00:00:00 2001 From: damocles Date: Mon, 13 Jul 2026 16:12:40 +0200 Subject: [PATCH] chore(#2400): skip the prebuild warm-build when the container is down --- hive-c0re/src/job_queue/exec.rs | 21 +++++++++++++++++---- hive-c0re/src/job_queue/model.rs | 5 ++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/hive-c0re/src/job_queue/exec.rs b/hive-c0re/src/job_queue/exec.rs index 7d5dd6d4..09d8c812 100644 --- a/hive-c0re/src/job_queue/exec.rs +++ b/hive-c0re/src/job_queue/exec.rs @@ -104,7 +104,11 @@ pub(super) async fn run_node(coord: &Arc, claim: &Claim) -> Result< /// Out-of-band toplevel build while the container keeps serving: meta /// sync + optional per-agent relock, then warm /// `system.build.toplevel` so the later `Swap` hits cache and skips -/// straight to the profile-swap. +/// straight to the profile-swap. The warm build is skipped when the +/// container is already down: its only purpose is to shrink the swap's +/// downtime window, so a stopped agent (no uptime to preserve) doesn't +/// pay the double eval — `Swap` builds inline instead. The meta sync + +/// dir prep still run unconditionally (the `Swap` depends on them). async fn run_prebuild( coord: &Arc, claim: &Claim, @@ -134,9 +138,18 @@ async fn run_prebuild( crate::meta::lock_update_for_rebuild(name).await?; } } - ctx.step("nix build"); - let flake_ref = format!("{}#{name}", crate::paths::meta_root().display()); - crate::lifecycle::prebuild_toplevel(name, &flake_ref, &|log_id| ctx.build_log(log_id)).await?; + // Warm the toplevel build only when the container is up — the whole + // point of prebuild is to shrink the swap's downtime window. A + // stopped agent has no uptime to preserve, so skip the (expensive) + // eval and let the downstream `Swap` build inline. + if crate::lifecycle::is_running(name).await { + ctx.step("nix build"); + let flake_ref = format!("{}#{name}", crate::paths::meta_root().display()); + crate::lifecycle::prebuild_toplevel(name, &flake_ref, &|log_id| ctx.build_log(log_id)) + .await?; + } else { + ctx.step("skipped prebuild (container down)"); + } Ok(NodeOutput::default()) } diff --git a/hive-c0re/src/job_queue/model.rs b/hive-c0re/src/job_queue/model.rs index 684da72e..7e474785 100644 --- a/hive-c0re/src/job_queue/model.rs +++ b/hive-c0re/src/job_queue/model.rs @@ -68,7 +68,10 @@ pub enum NodeKind { /// meta `sync_agents`, optional per-agent relock, then /// `lifecycle::prebuild_toplevel`. `relock = false` only for /// meta-update cascade rebuilds (re-locking would revert the bump - /// the cascade just committed). + /// the cascade just committed). The `prebuild_toplevel` warm is + /// skipped when the container is already down — it only exists to + /// shrink the swap's downtime, which a stopped agent doesn't need + /// (the sync + dir prep still run; `Swap` builds inline). Prebuild { relock: bool }, /// `nixos-container update` profile-swap (requires the container /// stopped). Re-applies nspawn flags + resource limits first —