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
- Log the raw failure (Metadata and Details) to see the malformed content and identify the producer.
- Ensure all services produce failures via the SDK's ErrorToFailure so Details is valid serializedHandlerError JSON.
- Align SDK versions across producer and consumer services.
- 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
- Always construct failures via the SDK's ErrorToFailure, never by hand
- Keep SDK versions aligned across all producing/consuming services
- Do not let middleware rewrite failure Details bytes
- Log raw failure metadata when conversion fails to aid debugging
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
- failed to deserialize OperationError: %w
- link type is empty
- link type contains invalid char (valid chars: alphanumeric,
- empty operation name
- empty operation token
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/6ed8d467cf6a1dae.
Report an issue: GitHub.