temporalio/temporal · error

%w: ref transition count out of range for version %v: %v not

Error message

%w: ref transition count out of range for version %v: %v not in [%v, %v]

What it means

StalenessCheck verifies that a task/request's imprinted VersionedTransition reference (namespace failover version + transition count) actually exists within the target workflow's stored transition history. This error is returned (wrapping history/consts.ErrStaleReference) when the reference's namespace failover version IS present in the history, but its TransitionCount falls outside the valid range [min+1, max] for that version, meaning the reference points to an impossible or already-superseded state (e.g. after split-brain reconciliation).

Source

Thrown at common/persistence/transitionhistory/transition_history.go:115

			)
		}
		return fmt.Errorf(
			"%w: state namespace failover version > ref namespace failover version: %v > %v",
			consts.ErrStaleReference,
			lastItem.NamespaceFailoverVersion,
			refVersionedTransition.NamespaceFailoverVersion,
		)
	}
	if idx == len(history)-1 && maxTransitionCount < refVersionedTransition.TransitionCount {
		return fmt.Errorf(
			"%w: state transition count < ref transition count: %v < %v",
			consts.ErrStaleState,
			maxTransitionCount,
			refVersionedTransition.TransitionCount,
		)
	}
	if minTransitionCount > refVersionedTransition.TransitionCount || maxTransitionCount < refVersionedTransition.TransitionCount {
		return fmt.Errorf(
			"%w: ref transition count out of range for version %v: %v not in [%v, %v]",
			consts.ErrStaleReference,
			refVersionedTransition.NamespaceFailoverVersion,
			refVersionedTransition.TransitionCount,
			minTransitionCount,
			maxTransitionCount,
		)
	}
	return nil
}

// transitionHistoryRangeForVersion finds the index and transition count range in the given history for the given version.
func transitionHistoryRangeForVersion(
	history []*persistencespb.VersionedTransition,
	version int64,
) (idx int, minTransitionCount int64, maxTransitionCount int64) {
	prevVersionMaxTransitionCount := int64(-1)
	for i, item := range history {

View on GitHub (pinned to bde624efd1)

Solutions

  1. Treat as a terminal ErrStaleReference: drop or discard the stale task/request rather than retrying against this state
  2. Reload the workflow mutable state from persistence and re-derive the current versioned transition reference before re-attempting
  3. Verify replication/reconciliation logic is not fabricating transition counts beyond the recorded per-version max
  4. Check for split-brain (two history shards active) or a sharding/history-rollback incident and reconcile state

Example fix

// before: retrying on any staleness error
callWithState(...)
// after: check terminal stale reference and reload
err := transitionhistory.StalenessCheck(ms.VersionedTransitions, ref)
if errors.Is(err, consts.ErrStaleState) || errors.Is(err, consts.ErrStaleReference) {
    ms, err = shards.ReloadWorkflowMutableState(ctx, ref.WorkflowKey)
    // handle reload, then retry once with fresh state
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before using a ref against a state, pre-check locally
func refInRange(history []*persistencespb.VersionedTransition, ref *persistencespb.VersionedTransition) bool {
    if len(history) == 0 || ref == nil { return false }
    for i, item := range history {
        if item.NamespaceFailoverVersion == ref.NamespaceFailoverVersion {
            min := int64(0)
            if i > 0 { min = history[i-1].TransitionCount + 1 }
            return ref.TransitionCount >= min && ref.TransitionCount <= item.TransitionCount
        }
    }
    return false
}

Type guard

func isStaleReference(err error) bool {
    return errors.Is(err, consts.ErrStaleReference)
}

Try / catch

if err := transitionhistory.StalenessCheck(state.VersionedTransitions, ref); err != nil {
    if errors.Is(err, consts.ErrStaleReference) {
        // terminal: discard/refetch, do not retry against same state
        ms, rerr := reloadMutableState(ctx, key)
        ...
    }
    return err
}

Prevention

When it happens

Trigger: Calling StalenessCheck (directly or via IsStale, GetOrPollWorkflowMutableState, applyBackfillEvents, or ReplicateVersionedTransition) with a ref whose NamespaceFailoverVersion matches an entry in the history, but whose TransitionCount is either below the previous version's max+1 (reference predates that version's window) or above that version's recorded max (reference claims more transitions than the state has ever recorded).

Common situations: A duplicate/stale task or replication task referencing transition counts the history node never recorded; a workflow whose mutable state was rebuilt or truncated after failover so its history shrank below the caller's reference; split-brain repair reconciling two nodes with divergent histories; clocks/version skew after namespace failover where a ref carries a version present in history but counts from a divergent branch.

Related errors


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