From 4d885df9ade2575c86915042f88dca6b20cf5a0b Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 27 Jul 2026 13:43:16 +0200 Subject: [PATCH] refactor(#2693): pass claudeCodePackage straight into serve.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `builtins.toJSON` already serialises a derivation as its out path and `null` as `null`, so the `if … then null else "${…}"` binding was doing by hand what the serialiser does anyway. Hand the package in whole and drop the intermediate. The gc-root property is unchanged and re-measured on the real module: `environment.etc."hyperhive/serve.json".text` still `hasContext`, so the host system closure still holds the package alive. Verified both ways — with the package set the rendered JSON is byte-identical to the interpolated version, and unset still emits `null` — with all module assertions passing in each case. The assertion now checks `builtins.toJSON cfg.claudeCodePackage`, which is the value that actually has to carry the context, rather than an intermediate that no longer exists. --- nix/host-modules/hive-c0re/default.nix | 34 ++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/nix/host-modules/hive-c0re/default.nix b/nix/host-modules/hive-c0re/default.nix index bbcb528c..28937561 100644 --- a/nix/host-modules/hive-c0re/default.nix +++ b/nix/host-modules/hive-c0re/default.nix @@ -59,22 +59,6 @@ let fi ''; - # Store path of the `claude-code` every agent runs, or null for "each - # agent keeps the one out of its own nixpkgs". meta.rs writes it into - # each agent's generated flake as a plain string literal, and the agent - # module puts its `bin/` on the harness PATH. - # - # This interpolation is also the package's gc root, and the only one: - # it carries store context, `builtins.toJSON` preserves that, so the - # /etc entry below genuinely references the package and the host's - # system closure holds it alive. Nothing on the container side can — - # a path spelled out in a generated flake is text, not a reference. - # Hence the assertion further down: do NOT discard this context, and - # do not hand meta.rs the path by a route that drops it. The failure - # mode is a garbage-collected `claude` and a hive that can't take a - # turn, weeks after the commit that caused it. - claudeCodePath = if cfg.claudeCodePackage == null then null else "${cfg.claudeCodePackage}"; - # The `hive-c0re serve` config JSON. Keys are snake_case to match the # `ServeConfig` serde shape the daemon deserialises (the # container-injected HiveEnv fields, flattened, plus the hive-c0re-local @@ -92,7 +76,20 @@ let hyperhive_flake = cfg.hyperhiveFlake; hyperhive_docs_flake = cfg.hyperhiveDocs; nixpkgs_flake = cfg.nixpkgsFlake; - claude_code_path = claudeCodePath; + # The `claude-code` every agent runs, or null for "each agent keeps + # the one out of its own nixpkgs". `builtins.toJSON` serialises a + # derivation as its out path (and null as null), so the package goes + # in whole rather than interpolated — that is also the ONLY thing + # gc-rooting it: the resulting string carries store context, so this + # /etc entry genuinely references the package and the host's system + # closure holds it alive. Nothing container-side can — meta.rs writes + # the path into each agent's generated flake as a plain string + # literal, and text is not a reference. Hence the assertion below: do + # NOT discard this context, and do not route the path through + # anything that drops it. The failure mode is a garbage-collected + # `claude` and a hive that can't take a turn, weeks after the commit + # that caused it. + claude_code_path = cfg.claudeCodePackage; dashboard_port = cfg.dashboardPort; operator_pronouns = cfg.operatorPronouns; context_window_tokens = cfg.contextWindowTokens; @@ -119,7 +116,8 @@ in # deploy — it only shows up as every agent failing to spawn # `claude`, at whatever unrelated moment the gc runs. Cheap # enough to just check. - assertion = cfg.claudeCodePackage == null || builtins.hasContext claudeCodePath; + assertion = + cfg.claudeCodePackage == null || builtins.hasContext (builtins.toJSON cfg.claudeCodePackage); message = '' services.hyperhive.c0re.claudeCodePackage lost its store context on the way into /etc/hyperhive/serve.json, so the