larksuite/cli · error

protocol encode: %w

Error message

protocol encode: %w

What it means

protocol.Encode wraps json.Marshal failures when serializing a message to the newline-delimited JSON wire protocol. Marshal only fails for unsupported types (channels, funcs, cyclic structures), so in practice this indicates a programmer error in the message struct, not bad wire data. The write error itself is returned unwrapped from w.Write.

Source

Thrown at internal/event/adapter/localbus/protocol/codec.go:48

// MaxFrameBytes bounds reader buffer growth. It must stay above
// MaxEventPayloadBytes, or the bus can frame an event the consumer then refuses
// to read.
const MaxFrameBytes = MaxEventPayloadBytes + maxFrameOverheadBytes

// ErrFrameTooLarge is returned by ReadFrame when a single frame exceeds MaxFrameBytes.
var ErrFrameTooLarge = errors.New("protocol: frame exceeds MaxFrameBytes")

const WriteTimeout = 5 * time.Second // bound writes against wedged peer kernel buffer

type typeEnvelope struct {
	Type string `json:"type"`
}

func Encode(w io.Writer, msg interface{}) error {
	data, err := json.Marshal(msg)
	if err != nil {
		return fmt.Errorf("protocol encode: %w", err)
	}
	data = append(data, '\n')
	_, err = w.Write(data)
	return err
}

func EncodeWithDeadline(conn net.Conn, msg interface{}, timeout time.Duration) error {
	if err := conn.SetWriteDeadline(time.Now().Add(timeout)); err != nil {
		return err
	}
	return Encode(conn, msg)
}

// ReadFrame reads one newline-delimited message; caps at MaxFrameBytes to defang slowloris.
func ReadFrame(br *bufio.Reader) ([]byte, error) {
	var buf []byte
	for {
		chunk, err := br.ReadSlice('\n')

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Inspect the %w cause: it names the unsupported type or cycle
  2. Fix the message struct: remove or replace chan/func/complex fields with serializable types or json:"-" tags
  3. Break cyclic references before encoding (store IDs instead of back-pointers)
  4. Test with a unit encode of the concrete message type (see TestEncodeDecode*) before shipping

Example fix

// before
type Event struct {
    Ack func() `json:"ack"`
}
// after
type Event struct {
    Ack func() `json:"-"`
}
Defensive patterns

Strategy: try-catch

Validate before calling

if err := json.Marshal(msg); err != nil {
	return fmt.Errorf("message not encodable: %w", err)
}
// run before handing msg to protocol.Encode

Type guard

func encodable(msg any) bool {
	return json.Marshal(msg) == nil
}

Try / catch

if err := protocol.Encode(w, msg); err != nil {
	var ue *json.UnsupportedTypeError
	var ce *json.UnsupportedValueError
	switch {
	case errors.As(err, &ue):
		log.Printf("unsupported field type %s in %T", ue.Type, msg)
	case errors.As(err, &ce):
		log.Printf("unsupported value in %T: %v", msg, ce.Value)
	default:
		// write failure on w: check pipe/socket state
	}
}

Prevention

When it happens

Trigger: Calling Encode (directly or via handle's message flow) with a msg value that json.Marshal cannot serialize: an unmarshalable field type (chan, func, complex), a cyclic pointer graph, or an invalid utf-8/systr trick that makes Marshal error.

Common situations: Someone added a field of unsupported type to Hello/Event/StatusResponse structs; passing a raw map containing function values; embedding a context or sync.Mutex-like value that trips marshaling rules.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/a69e0db99bb22b02. Report an issue: GitHub.