microsoft/typescript-go · error

unexpected message type: %d

Error message

unexpected message type: %d

What it means

ReadMessage parsed a valid tuple whose message type is one this reader never accepts from its peer: MessageTypeResponse, MessageTypeError, or MessageTypeCall. Only Request, CallResponse, and CallError are handled inbound; responses to this side's own Calls are consumed inline by SyncConn.Call, never by the main read loop.

Source

Thrown at internal/api/protocol_msgpack.go:90

		msg.Method = method
		msg.Params = payload
	case MessageTypeCallResponse:
		// Response to our Call - use method as ID
		// Note: Method must be empty for IsResponse() to return true
		id := jsonrpc.NewIDString(method)
		msg.ID = id
		msg.Result = payload
	case MessageTypeCallError:
		// Error response to our Call
		// Note: Method must be empty for IsResponse() to return true
		id := jsonrpc.NewIDString(method)
		msg.ID = id
		msg.Error = &jsonrpc.ResponseError{
			Code:    jsonrpc.CodeInternalError,
			Message: string(payload),
		}
	default:
		return nil, fmt.Errorf("unexpected message type: %d", msgType)
	}

	return msg, nil
}

func (p *MessagePackProtocol) readTuple() (MessageType, string, []byte, error) {
	// Read fixed array marker (0x93 = 3-element array)
	t, err := p.r.ReadByte()
	if err != nil {
		return 0, "", nil, err
	}
	if t != msgpackFixedArray3 {
		return 0, "", nil, fmt.Errorf("%w: expected fixed 3-element array (0x93), received: 0x%02x", ErrInvalidRequest, t)
	}

	// Read message type - can be positive fixint (0x00-0x7F) or uint8 (0xCC + value)
	t, err = p.r.ReadByte()
	if err != nil {

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Verify exactly one side owns SyncConn.Run and answers requests, the other issues them
  2. Ensure the peer writes only request/call-response/call-error tuples toward this reader
  3. In loopback tests, route each side's writer to the OTHER side's reader
  4. Close and re-establish the connection after fixing wiring - the stream cannot be trusted

Example fix

// before
go server.Run(ctx); go server.Run(ctx) // both reading same role frames

// after
go server.Run(ctx)          // one side serves
go clientLoop(ctx, clientConn) // other side calls
Defensive patterns

Strategy: try-catch

Try / catch

if err := conn.Run(ctx); err != nil {
	if strings.Contains(err.Error(), "unexpected message type") {
		// role wiring is wrong or the peer echoed our own frames; rebuild the link
		return rebuildLinkWithCorrectRoles()
	}
	return err
}

Prevention

When it happens

Trigger: Both ends of the pipe wired in the same role (two 'servers' or two 'clients'), so each writes Call/Response frames toward the other; a client echoing a server-written frame back; loopback tests piping a side's output to its own input.

Common situations: Constructing the two protocol endpoints with swapped roles; test harnesses connecting a writer to itself; debugging proxies that reflect frames.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/6161c6aea9789aba. Report an issue: GitHub.