temporalio/temporal · error

dynamicconfig.New*Setting must only be called from static in

Error message

dynamicconfig.New*Setting must only be called from static initializers

What it means

The dynamicconfig registry records every New*Setting call and, once queryRegistry has run (globalRegistry.queried set), treats further registrations as a bug: settings must exist before the first query so the registry is a complete static inventory. Calling New*Setting after querying panics.

Source

Thrown at common/dynamicconfig/registry.go:21

import (
	"fmt"
	"sync/atomic"
)

type (
	registry struct {
		settings map[Key]GenericSetting
		queried  atomic.Bool
	}
)

var (
	globalRegistry registry
)

func register(s GenericSetting) {
	if globalRegistry.queried.Load() {
		panic("dynamicconfig.New*Setting must only be called from static initializers")
	}
	if globalRegistry.settings == nil {
		globalRegistry.settings = make(map[Key]GenericSetting)
	}
	if globalRegistry.settings[s.Key()] != nil {
		// nolint:forbidigo // only called during static initialization
		panic(fmt.Sprintf("duplicate registration of dynamic config key: %q", s.Key().String()))
	}
	globalRegistry.settings[s.Key()] = s
}

func queryRegistry(k Key) GenericSetting {
	if !globalRegistry.queried.Load() {
		globalRegistry.queried.Store(true)
	}
	return globalRegistry.settings[k]
}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Move the New*Setting call to a package-level var so it runs during static initialization
  2. If settings must vary at runtime, don't register them; read values with the non-registered lookup APIs
  3. In tests, declare settings in init()/package vars before any registry query occurs

Example fix

// before
func handler() {
    s := dynamicconfig.NewBoolSetting("my.flag", false, ...)
    ...
}
// after
var myFlag = dynamicconfig.NewBoolSetting("my.flag", false, ...)

func handler() { ... use myFlag ... }
Defensive patterns

Strategy: validation

Validate before calling

// declare settings at package level only

Prevention

When it happens

Trigger: Constructing a dynamicconfig setting (NewBoolSetting, NewIntSetting, etc.) lazily at runtime — inside a request handler, a function called after server start, or a lazily initialized package var — after any code has queried the registry.

Common situations: Moving a setting declaration inside a function during refactoring; creating settings dynamically per-tenant/namespace; unit tests that build settings after the global registry was already queried by an earlier init.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/356abc612409b134. Report an issue: GitHub.