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
- Treat as stale: discard the timer/task; the state machine it targeted no longer exists
- Check replication lag or state rebuild events for the workflow if this occurs unexpectedly
- 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
- Re-validate refs immediately before use; keep ref lifetimes short
- Detect and discard tasks for already-deleted state machines
- Monitor mutable-state rebuild frequency
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
- %w: zombie workflow cannot be updated
- %w: %w
- ErrStaleState
- Failed to get HSM operation log: %v
- ErrInvalidTransition
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/7268096463ab0edb.
Report an issue: GitHub.