feat(#2149): set base claude-code env in managed settings

Add `baseClaudeEnv` (always-on) and extend `otelSettingsEnv` with the
env vars from #2149:

Always-on (every agent):
- CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
- CLAUDE_CODE_PLUGIN_PREFER_HTTPS=1
- CLAUDE_CODE_RESUME_INTERRUPTED_TURN=1  (recovers MCP-flap mid-turn)
- CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT=1
- CLAUDE_CODE_SYNC_PLUGIN_INSTALL=1
- CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX=hive+<agent>
- DISABLE_AUTOUPDATER=1 / DISABLE_UPDATES=1  (nix owns packages)
- DISABLE_INSTALL_GITHUB_APP_COMMAND=1
- DO_NOT_TRACK=1
- ENABLE_CLAUDEAI_MCP_SERVERS=0  (hive supplies its own)
- FORCE_AUTOUPDATE_PLUGINS=1

OTEL-gated (when hyperhive.otel.enable):
- CLAUDE_CODE_ENABLE_FEEDBACK_SURVEY_FOR_OTEL=1
- CLAUDE_CODE_OTEL_DIAG_STDERR=1
- OTEL_METRICS_INCLUDE_VERSION=1

Because baseClaudeEnv includes per-agent values
(CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX = "hive+${userName}"),
jq is now always run at build time — the OTEL-off branch that returned
the static asset verbatim is removed.
This commit is contained in:
atlas 2026-07-02 21:37:20 +02:00 committed by mara
commit 04692871cf

View file

@ -37,8 +37,38 @@ let
# (claude merges the `env` from the user settings on top of these # (claude merges the `env` from the user settings on top of these
# managed ones), so the token is read from disk at start and never # managed ones), so the token is read from disk at start and never
# touches the store. # touches the store.
# Base claude-code environment applied to every agent regardless of OTEL.
# Shipped via the managed settings `env` block so claude and `hivectl
# choom` both inherit them without a launch wrapper.
baseClaudeEnv = {
# Suppress analytics, survey pings, and other non-essential outbound
# traffic — agents are headless and don't need any of that.
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
DO_NOT_TRACK = "1";
# Disable claude's self-update machinery; package management is nix's job.
DISABLE_AUTOUPDATER = "1";
DISABLE_UPDATES = "1";
# Keep plugin updates synchronized on install; prefer HTTPS for fetches.
CLAUDE_CODE_PLUGIN_PREFER_HTTPS = "1";
CLAUDE_CODE_SYNC_PLUGIN_INSTALL = "1";
FORCE_AUTOUPDATE_PLUGINS = "1";
# Suppress the "install GitHub app" prompt — not applicable in-hive.
DISABLE_INSTALL_GITHUB_APP_COMMAND = "1";
# Disable Anthropic's hosted claude.ai MCP servers; the hive supplies its own.
ENABLE_CLAUDEAI_MCP_SERVERS = "0";
# Resume an interrupted turn on reconnect (recovers from transient MCP flaps).
CLAUDE_CODE_RESUME_INTERRUPTED_TURN = "1";
# Use the simpler system prompt variant suited to headless operation.
CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT = "1";
# Tag remote-control sessions with the hive+agent name for identification.
CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX = "hive+${userName}";
};
otelSettingsEnv = { otelSettingsEnv = {
CLAUDE_CODE_ENABLE_TELEMETRY = "1"; CLAUDE_CODE_ENABLE_TELEMETRY = "1";
# Attach feedback-survey data to the OTEL pipeline.
CLAUDE_CODE_ENABLE_FEEDBACK_SURVEY_FOR_OTEL = "1";
# Emit OTEL SDK diagnostics to stderr for easier log capture.
CLAUDE_CODE_OTEL_DIAG_STDERR = "1";
OTEL_METRICS_EXPORTER = "otlp"; OTEL_METRICS_EXPORTER = "otlp";
OTEL_LOGS_EXPORTER = "otlp"; OTEL_LOGS_EXPORTER = "otlp";
OTEL_TRACES_EXPORTER = "otlp"; OTEL_TRACES_EXPORTER = "otlp";
@ -51,6 +81,8 @@ let
OTEL_RESOURCE_ATTRIBUTES = OTEL_RESOURCE_ATTRIBUTES =
"service.name=hyperhive-agent,agent=${userName},hive=${hiveDisplayName},swarm=${swarmDisplayName}" "service.name=hyperhive-agent,agent=${userName},hive=${hiveDisplayName},swarm=${swarmDisplayName}"
+ lib.optionalString (otelCfg.extraResourceAttributes != "") ",${otelCfg.extraResourceAttributes}"; + lib.optionalString (otelCfg.extraResourceAttributes != "") ",${otelCfg.extraResourceAttributes}";
# Include the Claude Code version label in emitted metrics.
OTEL_METRICS_INCLUDE_VERSION = "1";
} }
// lib.optionalAttrs (otelCfg.metricIntervalMs != null) { // lib.optionalAttrs (otelCfg.metricIntervalMs != null) {
OTEL_METRIC_EXPORT_INTERVAL = toString otelCfg.metricIntervalMs; OTEL_METRIC_EXPORT_INTERVAL = toString otelCfg.metricIntervalMs;
@ -1176,25 +1208,26 @@ in
# deliberately NOT shipped here: effort is controlled live via the # deliberately NOT shipped here: effort is controlled live via the
# `--effort` CLI flag (HIVE_DEFAULT_EFFORT / the per-agent UI slider), # `--effort` CLI flag (HIVE_DEFAULT_EFFORT / the per-agent UI slider),
# which managed scope would otherwise override and lock. # which managed scope would otherwise override and lock.
# Base hive-enforced settings, plus — when OTEL is enabled — an `env` # Hive-enforced settings merged with a per-agent `env` block at BUILD
# block so Claude Code exports telemetry natively from the settings # time via `jq` (not eval-time `readFile`, which would be import-from-
# file it reads in every context (harness turn-loop AND `hivectl # derivation). The `env` block is always present: `baseClaudeEnv` sets
# choom`), with no launch wrapper. With OTEL off it's the shared # behaviour flags and the remote-control session prefix for every agent;
# static asset verbatim; with OTEL on it's a per-agent merge (the # `otelSettingsEnv` is merged on top when OTEL is enabled. claude-code
# resource attributes carry the agent name) done at BUILD time via # auto-discovers this file in every context (harness turn-loop AND
# `jq` — not eval-time `readFile`, which would be import-from- # `hivectl choom`) so no launch wrapper is needed.
# derivation.
environment.etc."claude-code/managed-settings.json".source = environment.etc."claude-code/managed-settings.json".source =
let let
baseSettings = "${pkgs.hyperhive-assets}/share/hyperhive/prompts/claude-settings.json"; baseSettings = "${pkgs.hyperhive-assets}/share/hyperhive/prompts/claude-settings.json";
# Merge base env (always) with OTEL env (when enabled). jq is always
# run — `baseClaudeEnv` contains per-agent values (e.g.
# CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX) that can't live in the
# static store asset.
allEnv = baseClaudeEnv // lib.optionalAttrs otelCfg.enable otelSettingsEnv;
in in
if !otelCfg.enable then pkgs.runCommand "managed-settings.json" { nativeBuildInputs = [ pkgs.jq ]; } ''
baseSettings jq --argjson env ${lib.escapeShellArg (builtins.toJSON allEnv)} \
else '. + { env: $env }' ${baseSettings} > "$out"
pkgs.runCommand "managed-settings.json" { nativeBuildInputs = [ pkgs.jq ]; } '' '';
jq --argjson env ${lib.escapeShellArg (builtins.toJSON otelSettingsEnv)} \
'. + { env: $env }' ${baseSettings} > "$out"
'';
# Inject the OTEL auth header (a secret) into the agent's *user* # Inject the OTEL auth header (a secret) into the agent's *user*
# claude settings at runtime, keeping it out of the world-readable # claude settings at runtime, keeping it out of the world-readable