block/buzz · critical
metrics exporter must install exactly once: {error}
Error message
metrics exporter must install exactly once: {error} What it means
install() is the legacy panic-on-failure entry point that installs the global metrics recorder and Prometheus exporter exactly once. It delegates to try_install() and panics with 'metrics exporter must install exactly once: {error}' on any failure — typically because a recorder/exporter was already installed, or the port could not be bound.
Source
Thrown at crates/buzz-relay/src/metrics.rs:219
.with_http_listener(([0, 0, 0, 0], port))
.build()
.map_err(MetricsInstallError::Build)?;
metrics::set_global_recorder(recorder)
.map_err(|_error| MetricsInstallError::RecorderConflict)?;
describe_readiness_metrics();
describe_db_pool_metrics();
tokio::spawn(exporter);
Ok(())
}
/// Install the global metrics recorder and spawn the Prometheus HTTP exporter.
///
/// This compatibility entry point preserves the original panic-on-failure API.
/// New startup code should use [`try_install`] to report typed failures.
pub fn install(port: u16, gauge_idle_timeout_secs: u64) {
try_install(port, gauge_idle_timeout_secs)
.unwrap_or_else(|error| panic!("metrics exporter must install exactly once: {error}"));
}
/// Register the frozen readiness metric descriptions with the active recorder.
pub(crate) fn describe_readiness_metrics() {
metrics::describe_counter!(
"buzz_readiness_checks_total",
"Kubernetes health-listener readiness probes by terminal bounded reason"
);
metrics::describe_counter!(
"buzz_readiness_dependency_checks_total",
"Completed readiness dependency attempts by dependency and bounded outcome"
);
metrics::describe_histogram!(
"buzz_readiness_check_duration_seconds",
metrics::Unit::Seconds,
"Completed readiness check duration without outcome label multiplication"
);
metrics::describe_gauge!(View on GitHub (pinned to dad5a33865)
Solutions
- Call try_install(port, secs) instead and handle the typed error — it is the recommended API for startup code
- Ensure install()/try_install() is invoked exactly once per process (guard in main/init)
- If running tests, install metrics once in a shared setup or use a once-guard
- Change the metrics port if another process holds it
Example fix
// before
metrics::install(port, gauge_idle_timeout_secs);
// after
if let Err(error) = metrics::try_install(port, gauge_idle_timeout_secs) {
tracing::error!(%error, "metrics exporter failed to install");
return Err(error.into());
} Defensive patterns
Strategy: try-catch
Validate before calling
// ensure single install with a process-wide guard
static INIT: OnceLock<()> = OnceLock::new();
if INIT.set(()).is_err() { return; } // already installed Type guard
null
Try / catch
match metrics::try_install(port, secs) {
Ok(()) => tracing::info!("metrics installed"),
Err(e) => tracing::error!(%e, "metrics install failed"),
} Prevention
- Prefer try_install over the panic-on-duplicate install() wrapper
- Install metrics exactly once in main()/test harness setup
- Verify the metrics port is free before startup (check for stale processes)
When it happens
Trigger: Calling install() (or relay startup) twice in the same process — e.g. tests installing the recorder then calling main(); or try_install failing because the Prometheus HTTP exporter port is already in use.
Common situations: Test binaries where each test calls setup that installs metrics; double invocation of relay main; another process (or a leftover relay) holds the metrics port.
Related errors
- failed to install rustls crypto provider
- bundled model-capabilities.json failed validation: {e}
- bundled model-capabilities.json must parse
- nip98 mode always resolves principal
- failed to install rustls crypto provider
AI-assisted analysis of block/buzz@dad5a33865 (2026-09-05).
Data as JSON: /api/errors/320d3f867e390ccc.
Report an issue: GitHub.