temporalio/temporal · error · NotFound (wrapped ErrStaleReference)

%w: zombie workflow cannot be updated

Error message

%w: zombie workflow cannot be updated

What it means

ErrWorkflowZombie wraps ErrStaleReference and indicates a workflow execution is in the zombie state — the workflow already completed but its mutable state still exists, so it can no longer accept updates. History service returns it from CHASM-related validation paths (validateNotZombieWorkflow, validateChasmSideEffectTask, executeChasmPureTimers).

Source

Thrown at service/history/consts/const.go:53

	// task is no longer needed.
	// It is also a NotFoundError to indicate to API callers that the object they're targeting is not found.
	ErrStaleReference = serviceerror.NewNotFound("stale reference")
	// ErrStaleState is the error returned during state update indicating that cached mutable state could be stale after
	// a reload attempt.
	ErrStaleState = staleStateError{}
	// ErrTransitionHistoryDisabled is the error to indicate that transition history is disabled for the state machine,
	// and request cannot be processed before it's re-enabled.
	ErrTransitionHistoryDisabled = serviceerror.NewFailedPrecondition("Transition history disabled")
	// ErrActivityTaskNotFound is the error to indicate activity task could be duplicate and activity already completed
	ErrActivityTaskNotFound = serviceerror.NewNotFound("invalid activityID or activity already timed out or invoking workflow is completed")
	// ErrActivityNotFound is the error to indicate that there is no pending activity with this ID
	ErrActivityNotFound = serviceerror.NewNotFound("Can't find pending activity with such ID. Invalid activityID or activity already completed")
	// ErrActivityTaskNotCancelRequested is the error to indicate activity to be canceled is not cancel requested
	ErrActivityTaskNotCancelRequested = serviceerror.NewInvalidArgument("unable to mark activity as canceled without activity being request canceled first")
	// ErrWorkflowCompleted is the error to indicate workflow execution already completed
	ErrWorkflowCompleted = serviceerror.NewNotFound("workflow execution already completed")
	// ErrWorkflowZombie is the error to indicate workflow execution is in zombie state and cannot be updated
	ErrWorkflowZombie = fmt.Errorf("%w: zombie workflow cannot be updated", ErrStaleReference)
	// ErrWorkflowExecutionNotFound is the error to indicate workflow execution does not exist
	ErrWorkflowExecutionNotFound = serviceerror.NewNotFound("workflow execution not found")
	// ErrWorkflowParent is the error to parent execution is given and mismatch
	ErrWorkflowParent = serviceerror.NewNotFound("workflow parent does not match")
	// ErrDeserializingToken is the error to indicate task token is invalid
	ErrDeserializingToken = serviceerror.NewInvalidArgument("error deserializing task token")
	// ErrSignalsLimitExceeded is the error indicating limit reached for maximum number of signal events
	ErrSignalsLimitExceeded = serviceerror.NewInvalidArgument("exceeded workflow execution limit for signal events")
	// ErrWorkflowClosing is the error indicating requests to workflow can not be applied as workflow is closing
	ErrWorkflowClosing = &serviceerror.ResourceExhausted{
		Cause:   enumspb.RESOURCE_EXHAUSTED_CAUSE_BUSY_WORKFLOW,
		Scope:   enumspb.RESOURCE_EXHAUSTED_SCOPE_NAMESPACE,
		Message: "workflow operation can not be applied because workflow is closing",
	}
	// ErrEventsAterWorkflowFinish is the error indicating server error trying to write events after workflow finish event
	ErrEventsAterWorkflowFinish = serviceerror.NewInternal("error validating last event being workflow finish event")
	// ErrQueryEnteredInvalidState is error indicating query entered invalid state
	ErrQueryEnteredInvalidState = serviceerror.NewInvalidArgument("query entered invalid state, this should be impossible")

View on GitHub (pinned to bde624efd1)

Solutions

  1. Treat as stale reference: discard the task/update rather than retrying — the workflow is done
  2. Check for replication lag or duplicate task delivery if this happens frequently for the same workflow
  3. If the workflow should not be complete, investigate why it finished (timeout, termination) before resending updates

Example fix

// before
err := workflowContext.Update(...)
// after
err := workflowContext.Update(...)
if errors.Is(err, consts.ErrWorkflowZombie) {
	// workflow already completed; drop the update
	return nil
}
Defensive patterns

Strategy: type-guard

Type guard

func isZombieWorkflow(err error) bool { return errors.Is(err, consts.ErrWorkflowZombie) }

Try / catch

if err := updateWorkflow(ctx, wf); err != nil {
	if errors.Is(err, consts.ErrWorkflowZombie) {
		// workflow already completed; drop the update idempotently
		return nil
	}
	return err
}

Prevention

When it happens

Trigger: Any attempt to update a workflow whose mutable state shows zombie status: validating a CHASM side-effect task, executing CHASM pure timers or state-machine timers against a workflow that completed before the task ran.

Common situations: A workflow completed (timeout, cancellation, termination) while completion/replication tasks were still in flight; replication lag lets one cluster finish the workflow while another still processes tasks for it; retry of an already-applied task after workflow completion.

Related errors


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