temporalio/temporal · error

ClusterMetadata encountered local namesapce with failover ve

Error message

ClusterMetadata encountered local namesapce with failover version %v

What it means

ClusterNameForFailoverVersion resolves which cluster a namespace's failover version belongs to. Global namespaces carry failover versions; local namespaces do not. If the method is invoked with a local (non-global) namespace and a non-nil failover version, the invariants are contradictory and it panics (note the pre-existing typo "namesapce" in the message).

Source

Thrown at common/cluster/metadata.go:340

	result := make(map[string]ClusterInformation, len(m.clusterInfo))
	maps.Copy(result, m.clusterInfo)
	return result
}

func (m *metadataImpl) ClusterNameForFailoverVersion(isGlobalNamespace bool, failoverVersion int64) string {
	if failoverVersion == common.EmptyVersion {
		// Local namespace uses EmptyVersion. But local namespace could be promoted to global namespace. Once promoted,
		// workflows with EmptyVersion could be replicated to other clusters. The receiving cluster needs to know that
		// those workflows are not from their current cluster.
		if isGlobalNamespace {
			return unknownClusterNamePrefix + strconv.Itoa(int(failoverVersion))
		}
		return m.currentClusterName
	}

	if !isGlobalNamespace {
		panic(fmt.Sprintf(
			"ClusterMetadata encountered local namesapce with failover version %v",
			failoverVersion,
		))
	}

	initialFailoverVersion := failoverVersion % m.failoverVersionIncrement
	// Failover version starts with 1.  Zero is an invalid value for failover version
	if initialFailoverVersion == common.EmptyVersion {
		initialFailoverVersion = m.failoverVersionIncrement
	}

	m.clusterLock.RLock()
	defer m.clusterLock.RUnlock()
	clusterName, ok := m.versionToClusterName[initialFailoverVersion]
	if !ok {
		m.logger.Warn(fmt.Sprintf(
			"Unknown initial failover version %v with given cluster initial failover version map: %v and failover version increment %v.",
			initialFailoverVersion,

View on GitHub (pinned to bde624efd1)

Solutions

  1. Check isGlobalNamespace on the namespace before calling ClusterNameForFailoverVersion, and only call it for global namespaces
  2. Inspect and fix the namespace persistence record so local namespaces have zero failover version
  3. Wrap in recover at high-level request handlers and return an internal-service error instead of crashing

Example fix

// before
name := metadata.ClusterNameForFailoverVersion(ns.FailureVersion, ns.IsGlobalNamespace())
// after
if !ns.IsGlobalNamespace() {
  return metadata.GetClusterName() // local namespaces always map to current cluster
}
name := metadata.ClusterNameForFailoverVersion(ns.FailureVersion, true)
Defensive patterns

Strategy: validation

Validate before calling

if !namespace.IsGlobalNamespace(namespaceEntry) {
  // local namespaces always map to the current cluster; skip the call
  clusterName := metadata.GetCurrentClusterName()
}

Try / catch

func safeClusterNameForVersion(m cluster.Metadata, fv int64, isGlobal bool) (name string, err error) {
  defer func() { if r := recover(); r != nil { err = fmt.Errorf("ClusterNameForFailoverVersion: %v", r) } }()
  return m.ClusterNameForFailoverVersion(fv, isGlobal), nil
}

Prevention

When it happens

Trigger: Calling ClusterNameForFailoverVersion for a namespace whose NamespaceInfo lacks IsGlobalNamespace but which has a failover version set; corrupted namespace state where a failover version exists without global flag.

Common situations: Upgraded/cloned namespace rows with stale failover versions; custom code paths reading failover versions without checking isGlobalNamespace first.

Related errors


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