temporalio/temporal · error
failed to deserialize OperationError: %w
Error message
failed to deserialize OperationError: %w
What it means
Same converter path as the HandlerError case but for failures with metadata type "nexus.OperationError": the JSON in f.Details must unmarshal into serializedOperationError (containing the operation State). If it does not, this error is returned because the failure cannot be represented as a nexus.OperationError.
Source
Thrown at common/nexus/nexusrpc/failure_converter.go:161
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)
if err != nil {
return nil, fmt.Errorf("failed to deserialize OperationError: %w", err)
}
oe := &nexus.OperationError{
Message: f.Message,
StackTrace: f.StackTrace,
State: nexus.OperationState(se.State),
OriginalFailure: &f,
}
if f.Cause != nil {
oe.Cause, err = e.FailureToError(*f.Cause)
if err != nil {
return nil, err
}
}
return oe, nil
}
}
// Note that the original failure cause is retained on the FailureError's failure object.
fe := &nexus.FailureError{Failure: f}View on GitHub (pinned to bde624efd1)
Solutions
- Inspect f.Metadata and f.Details to confirm the actual wire format of the failure.
- Regenerate/produce failures with the matching SDK version so Details uses the expected JSON schema.
- Fix any middleware that rewrites Details to preserve the original JSON bytes.
- Fall back to handling the raw nexus.Failure when typed conversion fails.
Example fix
// before
oe := err.(*nexus.OperationError) // hard cast after conversion fails
// after
oe, ok := err.(*nexus.OperationError)
if !ok {
return fmt.Errorf("operation failed: %s", err.Error())
} Defensive patterns
Strategy: type-guard
Validate before calling
if failure.Metadata["type"] == "nexus.OperationError" && !json.Valid(failure.Details) {
log.Printf("malformed OperationError details; treating as generic failure")
} Type guard
func isOperationError(err error) (*nexus.OperationError, bool) {
oe, ok := err.(*nexus.OperationError)
return oe, ok
} Try / catch
err, cerr := converter.FailureToError(failure)
if cerr != nil {
// fall back: inspect State from the raw failure if present, else generic
return errors.New(failure.Message)
}
var oe *nexus.OperationError
if errors.As(err, &oe) && oe.State == nexus.OperationStateFailed {
// handle failed operation
} Prevention
- Produce OperationError failures only through nexus.NewOperationError / SDK APIs
- Pin matching SDK versions across services sharing Nexus failures
- Avoid round-tripping failures through systems that re-encode Details
- Always type-assert OperationError instead of hard-casting
When it happens
Trigger: Calling FailureToError on a failure tagged "nexus.OperationError" whose Details JSON is invalid or has the wrong fields (e.g. missing/mistyped "state"), typically produced by an incompatible writer.
Common situations: SDK version skew where the serialized OperationError schema changed; manually built failures in tests; failures round-tripped through systems that re-encode Details as non-JSON (e.g. base64 proto).
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 HandlerError: %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/cd7a333d495c5735.
Report an issue: GitHub.