From 0a9832768500e6a4b86f90a0011e0f9ad84d63ee Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 31 May 2026 11:54:33 +0200 Subject: [PATCH] lifecycle: only prebuild when there's downtime to shave (mara on #721) --- hive-c0re/src/lifecycle.rs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 8c9040fb..1f59dcc9 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -425,22 +425,25 @@ pub async fn rebuild_no_meta( // the new profile and the new EXTRA_NSPAWN_FLAGS in one go. // If the container was already stopped, `update` builds + sets // the profile and we leave it stopped. - // - // Pre-build the system toplevel **before** stopping the - // container so the container is only down for the - // profile-swap + restart, not for the full nix evaluation + - // fetch + build cycle (#706). Failure here aborts before we - // touch the running container — the agent keeps serving its - // previous generation while the operator looks at the eval - // error. `nixos-container update` then finds the toplevel - // already in the store and skips straight to the profile - // swap. let was_running = is_running(name).await; - prebuild_toplevel(name, &flake_ref).await?; set_nspawn_flags(&container, agent_dir, claude_dir, notes_dir)?; set_resource_limits(&container)?; systemd_daemon_reload().await?; if was_running { + // Pre-build the system toplevel **before** stopping the + // running container so the agent keeps serving its + // previous generation while the eval + fetch + build + // happens out-of-band (#706). `nixos-container update` + // then finds the toplevel cached and skips straight to + // the profile-swap + restart — downtime collapses to that + // window only. Build failures surface here, before we + // touch the container. + // + // When the container is already stopped there's no + // downtime to shave (mara on #721#9007) — let `update` + // do the build inline rather than evaluating the flake + // twice for nothing. + prebuild_toplevel(name, &flake_ref).await?; run(&["stop", &container]).await?; } run(&["update", &container, "--flake", &flake_ref]).await?; @@ -450,12 +453,11 @@ pub async fn rebuild_no_meta( Ok(()) } } else { - // First spawn: pre-build for parity (`nixos-container create` - // builds + creates the container, so warming the store first - // doesn't shave downtime — there's none to shave — but it - // surfaces eval / fetch errors before `create` half-spawns a - // container record that the operator then has to clean up). - prebuild_toplevel(name, &flake_ref).await?; + // First spawn: no running container, no downtime to shave. + // `nixos-container create` builds + creates atomically — if + // the build fails, no container record is left around to + // clean up — so a pre-build adds nothing but a duplicate + // eval (mara on #721#9007). run(&["create", &container, "--flake", &flake_ref]).await?; set_nspawn_flags(&container, agent_dir, claude_dir, notes_dir)?; set_resource_limits(&container)?;