temporalio/temporal · critical
unable to create metrics handler
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.
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 AWS HTTP client for Elasticsearch
- unable to create dynamic config client
- unable to load config
- config validation error
- could not load config file
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/0878859bb345b9b7.
Report an issue: GitHub.
Appendix: 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)