temporalio/temporal · error

ErrStaleReference

ErrStaleReference

Error message

%w: %w

What it means

validateStateMachineRef fails when a stored state-machine reference can no longer be resolved: the HSM child node for ref.StateMachinePath() is not found. It wraps hsm.ErrStateMachineNotFound with consts.ErrStaleReference, meaning the reference points at state that no longer exists (deleted, rebuilt, or never replicated).

Source

Thrown at service/history/statemachine_environment.go:254

		(ref.StateMachineRef.MachineLastUpdateVersionedTransition != nil &&
			ref.StateMachineRef.MachineLastUpdateVersionedTransition.TransitionCount == 0) ||
		len(ms.GetExecutionInfo().TransitionHistory) == 0 {
		// Transtion history was disabled when the ref is generated,
		// fallback to the old validation logic.
		return e.validateStateMachineRefWithoutTransitionHistory(ms, ref, potentialStaleState)
	}

	err := transitionhistory.StalenessCheck(
		ms.GetExecutionInfo().GetTransitionHistory(),
		ref.StateMachineRef.MutableStateVersionedTransition,
	)
	if err != nil {
		return err
	}
	node, err := ms.HSM().Child(ref.StateMachinePath())
	if err != nil {
		if errors.Is(err, hsm.ErrStateMachineNotFound) {
			return fmt.Errorf("%w: %w", consts.ErrStaleReference, err)
		}
		return fmt.Errorf("%w: %w", serviceerror.NewInternal("node lookup failed"), err)
	}

	if node.InternalRepr().GetInitialVersionedTransition().TransitionCount == 0 {
		// transition history was disabled after the ref was generated and mutable state got rebuilt.
		// fallback to the old validation logic.
		return e.validateStateMachineRefWithoutTransitionHistory(ms, ref, potentialStaleState)
	}

	if transitionhistory.Compare(
		ref.StateMachineRef.MachineInitialVersionedTransition,
		node.InternalRepr().GetInitialVersionedTransition(),
	) != 0 {
		return fmt.Errorf("%w: initial versioned transition mismatch", consts.ErrStaleReference)
	}

	if ref.StateMachineRef.GetMachineLastUpdateVersionedTransition().GetTransitionCount() == 0 {

View on GitHub (pinned to bde624efd1)

Solutions

  1. Treat as stale: discard the timer/task; the state machine it targeted no longer exists
  2. Check replication lag or state rebuild events for the workflow if this occurs unexpectedly
  3. If the node should exist, inspect the workflow's mutable state / transition-history settings for a rebuild that dropped the node
Defensive patterns

Strategy: try-catch

Type guard

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

Try / catch

err := env.validateStateMachineRef(ms, ref, stale)
if err != nil && errors.Is(err, consts.ErrStaleReference) {
	// discard the timer/task; ref points at deleted state
	return nil
}

Prevention

When it happens

Trigger: executeSingleStateMachineTimer (or another caller) validates a task's state machine ref; ms.HSM().Child(ref.StateMachinePath()) returns hsm.ErrStateMachineNotFound. With transition history enabled, this is unconditionally treated as stale.

Common situations: State machine deleted (e.g. activity canceled/completed) between ref generation and timer execution; mutable state rebuilt without transition history; cross-cluster replication lag so the node hasn't arrived yet.

Related errors


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