temporalio/temporal · critical
unable to create metrics handler: %w
Error message
unable to create metrics handler: %w
What it means
During temporal server (fx) dependency injection, ServerOptionsProvider builds a metrics handler from config.Global.Metrics via metrics.MetricsHandlerFromConfig when no custom handler is injected. Any configuration or initialization failure is wrapped as 'unable to create metrics handler', aborting server startup.
Source
Thrown at temporal/fx.go:213
stopChan := make(chan any)
// ClientFactoryProvider
clientFactoryProvider := so.clientFactoryProvider
if clientFactoryProvider == nil {
clientFactoryProvider = client.NewFactoryProvider()
}
persistenceFactoryProvider := so.persistenceFactoryProvider
if persistenceFactoryProvider == nil {
persistenceFactoryProvider = PersistenceFactoryProvider()
}
// MetricsHandler
metricHandler := so.metricHandler
if metricHandler == nil {
metricHandler, err = metrics.MetricsHandlerFromConfig(logger, so.config.Global.Metrics)
if err != nil {
return serverOptionsProvider{}, fmt.Errorf("unable to create metrics handler: %w", err)
}
}
// EventLoggerProvider backs structured ("wide") events. Select the custom OTEL LoggerProvider
// if injected, else a no-op provider that discards events. A deployment opts in by injecting a
// provider via WithCustomEventLoggerProvider.
eventLoggerProvider := so.eventLoggerProvider
if eventLoggerProvider == nil {
eventLoggerProvider = lognoop.NewLoggerProvider()
}
// DynamicConfigClient
dcClient := so.dynamicConfigClient
if dcClient == nil {
dcConfig := so.config.DynamicConfigClient
if dcConfig != nil {
dcClient, err = dynamicconfig.NewFileBasedClientWithMetrics(dcConfig, logger, stopChan, metricHandler)
if err != nil {View on GitHub (pinned to bde624efd1)
Solutions
- Read the wrapped inner error and fix the metrics section of your config yaml
- Verify the exporter type and bind address (prometheus) are valid and the port is free
- Inject a custom handler with temporal.WithMetricHandler (fx option) if you need nonstandard metrics setup
- Temporarily remove/comment the metrics block to confirm it is the failing component
Example fix
// before
metrics:
prometheus:
bindAddress: "bad-address"
// after
metrics:
prometheus:
bindAddress: "0.0.0.0:9090" Defensive patterns
Strategy: validation
Validate before calling
// pre-validate metrics config before starting the server
var cfg config.Metrics
if err := yaml.Unmarshal(metricsYaml, &cfg); err != nil { log.Fatal(err) }
if cfg.Prometheus != nil {
if _, _, err := net.SplitHostPort(cfg.Prometheus.BindAddress); err != nil {
log.Fatalf("invalid prometheus bindAddress: %v", err)
}
} Prevention
- Lint/validate the server config yaml in CI (temporal server config validation)
- Check that prometheus bind ports are free in the deployment environment
- Derive config from the shipped config/template files instead of hand-writing from scratch
When it happens
Trigger: Invalid metrics config section (bad prometheus listen address, unknown exporter type, malformed tags/exclusions), or MetricsHandlerFromConfig returning an error for the chosen metrics reporter settings.
Common situations: Typo in config/template yaml (metrics.prometheus.bindAddress invalid), port already in use, unsupported SDK metrics scope settings, missing required fields in the metrics block.
Related errors
- unable to create dynamic config client: %w
- unable to create AWS HTTP client for Elasticsearch: %w
- unable to load config: %w
- only one of certData or certFile properties should be specif
- failed to initialize current cluster metadata
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/0878859bb345b9b7.
Report an issue: GitHub.