microsoft/typescript-go · error

unexpected message type

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.

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.

Appendix: 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)