temporalio/temporal · critical

<dynamic updateVersionToClusterName err>

Error message

<dynamic updateVersionToClusterName err>

What it means

After basic validation, NewMetadata calls updateVersionToClusterName, which builds a failover-version-to-cluster-name map from the supplied clusterInfo. If that helper returns an error (e.g. overlapping initial failover versions or duplicate version assignments across clusters), the constructor panics with the error text. This ensures ambiguous failover version mappings can never reach the running server.

Source

Thrown at common/cluster/metadata.go:158

	clusterInfo map[string]ClusterInformation,
	clusterMetadataStore persistence.ClusterMetadataManager,
	refreshDuration dynamicconfig.DurationPropertyFn,
	logger log.Logger,
) Metadata {
	if len(clusterInfo) == 0 {
		panic("Empty cluster information")
	} else if len(masterClusterName) == 0 {
		panic("Master cluster name is empty")
	} else if len(currentClusterName) == 0 {
		panic("Current cluster name is empty")
	} else if failoverVersionIncrement == 0 || failoverVersionIncrement > math.MaxInt32 {
		panic("Version increment <= 0 or > 2147483647")
	}

	versionToClusterName, err := updateVersionToClusterName(clusterInfo, failoverVersionIncrement)
	if err != nil {
		// nolint:forbidigo // matches the other startup-config panics in this constructor
		panic(err.Error())
	}
	if _, ok := clusterInfo[currentClusterName]; !ok {
		panic("Current cluster is not specified in cluster info")
	}
	if _, ok := clusterInfo[masterClusterName]; !ok {
		panic("Master cluster is not specified in cluster info")
	}

	copyClusterInfo := make(map[string]ClusterInformation)
	maps.Copy(copyClusterInfo, clusterInfo)
	if refreshDuration == nil {
		refreshDuration = dynamicconfig.GetDurationPropertyFn(refreshInterval)
	}
	return &metadataImpl{
		status:                   common.DaemonStatusInitialized,
		enableGlobalNamespace:    enableGlobalNamespace,
		failoverVersionIncrement: failoverVersionIncrement,
		masterClusterName:        masterClusterName,

View on GitHub (pinned to bde624efd1)

Solutions

  1. Assign each cluster in clusterInfo a unique InitialFailoverVersion that is consistent with the failoverVersionIncrement
  2. Log/inspect the panicked error text to identify which cluster/version conflicted
  3. Validate cluster info with updateVersionToClusterName in a preflight check before restarting the service

Example fix

// before
clusterInfo := map[string]cluster.ClusterInformation{
  "clusterA": {InitialFailoverVersion: 1},
  "clusterB": {InitialFailoverVersion: 1},
}
// after
clusterInfo := map[string]cluster.ClusterInformation{
  "clusterA": {InitialFailoverVersion: 1},
  "clusterB": {InitialFailoverVersion: 100000000},
}
Defensive patterns

Strategy: validation

Validate before calling

seen := map[int64]string{}
for name, info := range clusterInfo {
  if other, dup := seen[info.InitialFailoverVersion]; dup {
    return fmt.Errorf("clusters %s and %s share InitialFailoverVersion %d", other, name, info.InitialFailoverVersion)
  }
  seen[info.InitialFailoverVersion] = name
}

Prevention

When it happens

Trigger: cluster.NewMetadata with a clusterInfo map where two clusters share the same InitialFailoverVersion (producing a collision), or otherwise inconsistent cluster info that updateVersionToClusterName rejects.

Common situations: Hand-editing multi-cluster configs and giving two remote clusters the same initial failover version; programmatically generating cluster info where versions are not spaced by failoverVersionIncrement.

Related errors


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