temporalio/temporal · error · serializer error

%w: payload marshal error: %w

Error message

%w: payload marshal error: %w

What it means

xTemporalPayload converts a *commonpb.Payload into Nexus Content by calling payload.Marshal() (proto marshal). If the proto message fails to marshal — e.g. it contains data that violates proto3 invariants — the error is wrapped as errSerializer with this message and propagated to Serialize's caller.

Source

Thrown at common/nexus/payload_serializer.go:166

			return xTemporalPayload(payload)
		}
		// type is unset
	case "binary/plain":
		if len(payload.Metadata) != 1 {
			return xTemporalPayload(payload)
		}
		content.Header["type"] = "application/octet-stream"
	default:
		return xTemporalPayload(payload)
	}

	return &content, nil
}

func xTemporalPayload(payload *commonpb.Payload) (*nexus.Content, error) {
	data, err := payload.Marshal()
	if err != nil {
		return nil, fmt.Errorf("%w: payload marshal error: %w", errSerializer, err)
	}
	return &nexus.Content{
		Header: nexus.Header{"type": "application/x-temporal-payload"},
		Data:   data,
	}, nil
}

var PayloadSerializer nexus.Serializer = payloadSerializer{}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Log the inner marshal error to identify which field of the Payload is invalid.
  2. Rebuild the Payload through Temporal's data converter APIs instead of hand-assembling it.
  3. Validate the payload (Metadata keys, encoding, Data) before serializing.
  4. Ensure the proto runtime versions match across dependencies (protoimpl conflicts).

Example fix

// before
payload := &commonpb.Payload{Metadata: nil, Data: data} // built by hand, fails marshal
// after
payload, err := converter.ToPayload(value) // build via data converter
if err != nil {
    return err
}
content, err := serializer.Serialize(payload)
Defensive patterns

Strategy: validation

Validate before calling

if payload == nil || len(payload.Metadata) == 0 && len(payload.Data) == 0 {
    return errors.New("refusing to serialize empty or hand-built payload")
}

Type guard

func isMarshalablePayload(p *commonpb.Payload) bool {
    return p != nil && proto.CheckInitialized(p) == nil
}

Try / catch

content, err := serializer.Serialize(payload)
if err != nil && errors.Is(err, errSerializer) {
    return nil, fmt.Errorf("payload produced by %s is corrupt: %w", origin, err)
}

Prevention

When it happens

Trigger: Calling Serialize with a *commonpb.Payload whose Marshal() returns an error — typically a payload with unknown/invalid enum values or a corrupt required state produced by manually assembling Metadata/Data.

Common situations: Manually constructed Payload structs with invalid fields; payloads deserialized from corrupt persistence or external sources; proto runtime version incompatibilities producing unencodable messages.

Related errors


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