temporalio/temporal · error

failed to deserialize HandlerError: %w

Error message

failed to deserialize HandlerError: %w

What it means

FailureToError converts a Nexus failure with metadata type "nexus.HandlerError" back into a nexus.HandlerError. The typed details are stored as JSON in f.Details; if that JSON cannot be unmarshaled into serializedHandlerError, the failure metadata is corrupt or from an incompatible producer, so this error is returned.

Source

Thrown at common/nexus/nexusrpc/failure_converter.go:141

		}
		return f, nil
	default:
		return nexus.Failure{
			Message: typedErr.Error(),
		}, nil
	}
}

// FailureToError implements FailureConverter.
// nolint:revive // Keeping all of the logic together for readability, even if it means the function is long.
func (e knownErrorFailureConverter) FailureToError(f nexus.Failure) (error, error) {
	if f.Metadata != nil {
		switch f.Metadata["type"] {
		case "nexus.HandlerError":
			var se serializedHandlerError
			err := json.Unmarshal(f.Details, &se)
			if err != nil {
				return nil, fmt.Errorf("failed to deserialize HandlerError: %w", err)
			}
			he := &nexus.HandlerError{
				Message:         f.Message,
				StackTrace:      f.StackTrace,
				Type:            nexus.HandlerErrorType(se.Type),
				RetryBehavior:   se.RetryBehavior(),
				OriginalFailure: &f,
			}
			if f.Cause != nil {
				he.Cause, err = e.FailureToError(*f.Cause)
				if err != nil {
					return nil, err
				}
			}
			return he, nil
		case "nexus.OperationError":
			var se serializedOperationError
			err := json.Unmarshal(f.Details, &se)

View on GitHub (pinned to bde624efd1)

Solutions

  1. Log the raw failure (Metadata and Details) to see the malformed content and identify the producer.
  2. Ensure all services produce failures via the SDK's ErrorToFailure so Details is valid serializedHandlerError JSON.
  3. Align SDK versions across producer and consumer services.
  4. As a fallback, treat the original *nexus.Failure as a generic failure instead of failing the conversion.

Example fix

// before
he, err := converter.FailureToError(failure) // panics on malformed details
// after
he, err := converter.FailureToError(failure)
if err != nil {
    logger.Warn("unrecognized nexus failure details", "error", err)
    return errors.New(failure.Message)
}
Defensive patterns

Strategy: fallback

Validate before calling

if failure.Metadata["type"] == "nexus.HandlerError" && !json.Valid(failure.Details) {
    log.Printf("malformed HandlerError details from producer; using generic failure")
}

Type guard

func isHandlerErrorFailure(f *nexus.Failure) bool {
    return f != nil && f.Metadata["type"] == "nexus.HandlerError" && json.Valid(f.Details)
}

Try / catch

err, cerr := converter.FailureToError(failure)
if cerr != nil {
    // fall back to the raw failure rather than losing information
    return errors.New(failure.Message + ": " + cerr.Error())
}
return err

Prevention

When it happens

Trigger: Receiving a Nexus failure whose Details field is not valid JSON matching the serializedHandlerError shape — e.g. a failure created by hand, by a different SDK version, or truncated in transit — then calling FailureToError.

Common situations: Version skew between services producing/consuming failures; a proxy or middleware rewriting the failure payload; manually constructed failures in tests; corrupted persistence of failure details.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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