refactor(#2693): pass claudeCodePackage straight into serve.json

`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.
This commit is contained in:
atlas 2026-07-27 13:43:16 +02:00 committed by mara
commit 4d885df9ad

View file

@ -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