fix(#2172): gate CLAUDE_CODE_OTEL_DIAG_STDERR on hyperhive.otel.debug

CLAUDE_CODE_OTEL_DIAG_STDERR was always set when OTEL is enabled, making
OTEL SDK errors (e.g. 404 from a misconfigured collector endpoint) appear
in every agent's stderr unconditionally. Move it behind a new opt-in flag.

Changes:
- nix/modules/hive-c0re.nix: add services.hyperhive.otel.debug (bool,
  default false); wire to HYPERHIVE_OTEL_DEBUG env on hive-c0re unit.
- hive-c0re/src/meta.rs: add debug field to OtelConfig; read
  HYPERHIVE_OTEL_DEBUG; emit hyperhive.otel.debug = true when set.
- nix/templates/harness-base.nix: add hyperhive.otel.debug internal
  option; move CLAUDE_CODE_OTEL_DIAG_STDERR out of otelSettingsEnv into
  a debug-gated lib.optionalAttrs block.

Default behaviour: OTEL exports silently (no stderr noise). Operators
troubleshooting collector connectivity set services.hyperhive.otel.debug
= true to re-enable the diagnostic output.
This commit is contained in:
atlas 2026-07-03 22:10:22 +02:00 committed by mara
commit cb0a66147a
3 changed files with 42 additions and 3 deletions

View file

@ -666,6 +666,9 @@ struct OtelConfig {
extra_resource_attributes: Option<String>, extra_resource_attributes: Option<String>,
headers_credential: Option<String>, headers_credential: Option<String>,
metric_interval_ms: Option<u64>, metric_interval_ms: Option<u64>,
/// `HYPERHIVE_OTEL_DEBUG=1` → `hyperhive.otel.debug = true` →
/// `CLAUDE_CODE_OTEL_DIAG_STDERR=1` in every agent's env.
debug: bool,
} }
/// Read the hive-wide OTEL config from env, or `None` when OTEL is off. /// Read the hive-wide OTEL config from env, or `None` when OTEL is off.
@ -692,12 +695,16 @@ fn otel_config() -> Option<OtelConfig> {
.ok() .ok()
.and_then(|v| v.parse::<u64>().ok()) .and_then(|v| v.parse::<u64>().ok())
.filter(|v| *v > 0); .filter(|v| *v > 0);
let debug = std::env::var("HYPERHIVE_OTEL_DEBUG")
.ok()
.is_some_and(|v| v == "1");
Some(OtelConfig { Some(OtelConfig {
endpoint, endpoint,
protocol, protocol,
extra_resource_attributes, extra_resource_attributes,
headers_credential, headers_credential,
metric_interval_ms, metric_interval_ms,
debug,
}) })
} }
@ -982,6 +989,9 @@ where
// parsed u64, so it can't inject anything into the rendered nix. // parsed u64, so it can't inject anything into the rendered nix.
let _ = writeln!(out, " hyperhive.otel.metricIntervalMs = {ms};"); let _ = writeln!(out, " hyperhive.otel.metricIntervalMs = {ms};");
} }
if otel.debug {
out.push_str(" hyperhive.otel.debug = true;\n");
}
} }
out.push_str( out.push_str(
r#" # The harness service inside the container runs as a r#" # The harness service inside the container runs as a

View file

@ -279,6 +279,18 @@ in
''; '';
}; };
debug = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Emit OTEL SDK diagnostic messages to every agent's stderr by
setting `CLAUDE_CODE_OTEL_DIAG_STDERR=1`. Useful when
troubleshooting collector connectivity or endpoint config;
leave off in normal operation to avoid noise in agent logs.
Only meaningful when `enable` is true.
'';
};
metricIntervalMs = lib.mkOption { metricIntervalMs = lib.mkOption {
type = lib.types.nullOr lib.types.ints.positive; type = lib.types.nullOr lib.types.ints.positive;
default = null; default = null;
@ -923,6 +935,9 @@ in
// lib.optionalAttrs (otel.metricIntervalMs != null) { // lib.optionalAttrs (otel.metricIntervalMs != null) {
HYPERHIVE_OTEL_METRIC_INTERVAL_MS = toString otel.metricIntervalMs; HYPERHIVE_OTEL_METRIC_INTERVAL_MS = toString otel.metricIntervalMs;
} }
// lib.optionalAttrs otel.debug {
HYPERHIVE_OTEL_DEBUG = "1";
}
) )
// { // {
# In-cluster forge URL — the gateway vhost (`forge.<domain>`), which # In-cluster forge URL — the gateway vhost (`forge.<domain>`), which

View file

@ -67,8 +67,6 @@ let
CLAUDE_CODE_ENABLE_TELEMETRY = "1"; CLAUDE_CODE_ENABLE_TELEMETRY = "1";
# Attach feedback-survey data to the OTEL pipeline. # Attach feedback-survey data to the OTEL pipeline.
CLAUDE_CODE_ENABLE_FEEDBACK_SURVEY_FOR_OTEL = "1"; 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";
@ -357,6 +355,16 @@ in
default. Host-driven via `services.hyperhive.otel.metricIntervalMs`. default. Host-driven via `services.hyperhive.otel.metricIntervalMs`.
''; '';
}; };
debug = lib.mkOption {
type = lib.types.bool;
default = false;
internal = true;
description = ''
Emit OTEL SDK diagnostics to stderr (`CLAUDE_CODE_OTEL_DIAG_STDERR=1`).
Host-driven via `services.hyperhive.otel.debug`.
'';
};
}; };
options.hyperhive.allowedRecipients = lib.mkOption { options.hyperhive.allowedRecipients = lib.mkOption {
@ -1222,7 +1230,13 @@ in
# run — `baseClaudeEnv` contains per-agent values (e.g. # run — `baseClaudeEnv` contains per-agent values (e.g.
# CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX) that can't live in the # CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX) that can't live in the
# static store asset. # static store asset.
allEnv = baseClaudeEnv // lib.optionalAttrs otelCfg.enable otelSettingsEnv; allEnv =
baseClaudeEnv
// lib.optionalAttrs otelCfg.enable otelSettingsEnv
// lib.optionalAttrs (otelCfg.enable && otelCfg.debug) {
# SDK diagnostics — noisy; only on when services.hyperhive.otel.debug = true.
CLAUDE_CODE_OTEL_DIAG_STDERR = "1";
};
in in
pkgs.runCommand "managed-settings.json" { nativeBuildInputs = [ pkgs.jq ]; } '' pkgs.runCommand "managed-settings.json" { nativeBuildInputs = [ pkgs.jq ]; } ''
jq --argjson env ${lib.escapeShellArg (builtins.toJSON allEnv)} \ jq --argjson env ${lib.escapeShellArg (builtins.toJSON allEnv)} \