{"record":{"id":"0ba72e6033cc95dd","repo":"fatedier/frp","slug":"unexpected-message-type-s-want-s","errorCode":null,"errorMessage":"unexpected message type %s, want %s","messagePattern":"unexpected message type (.+?), want (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/msg/wire_v2.go","lineNumber":165,"sourceCode":"\t\treturn fmt.Errorf(\"message frame payload too short\")\n\t}\n\n\ttypeID := binary.BigEndian.Uint16(f.Payload[:2])\n\toutType := reflect.TypeOf(out)\n\tif outType == nil || outType.Kind() != reflect.Pointer {\n\t\treturn fmt.Errorf(\"message target must be a pointer\")\n\t}\n\telemType := outType.Elem()\n\texpectedTypeID, ok := v2MsgTypeIDMap[elemType]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unknown v2 message type %s\", elemType.String())\n\t}\n\tif typeID != expectedTypeID {\n\t\tactualType, ok := v2MsgReflectTypeMap[typeID]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unknown v2 message type %d\", typeID)\n\t\t}\n\t\treturn fmt.Errorf(\"unexpected message type %s, want %s\", actualType.String(), elemType.String())\n\t}\n\treturn json.Unmarshal(f.Payload[2:], out)\n}\n\nfunc EncodeV2MessageFrame(m Message) (*wire.Frame, error) {\n\tt := reflect.TypeOf(m)\n\tif t == nil {\n\t\treturn nil, fmt.Errorf(\"nil message\")\n\t}\n\tif t.Kind() == reflect.Pointer {\n\t\tt = t.Elem()\n\t}\n\ttypeID, ok := v2MsgTypeIDMap[t]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unknown v2 message type %s\", t.String())\n\t}\n\tcontent, err := json.Marshal(m)\n\tif err != nil {","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/msg/wire_v2.go#L147-L183","documentation":"Thrown by DecodeV2MessageFrameInto in pkg/msg/wire_v2.go when the frame's message type ID is a known type but does not equal the expected ID for the caller's target struct. The decoder is strict: if you decode into *msg.Ping but the frame carries msg.NatHoleVisitor, you get 'unexpected message type NatHoleVisitor, want Ping' instead of silently unmarshaling one message into another struct's fields.","triggerScenarios":"Calling msg.DecodeV2MessageFrameInto(f, out) where out's element type maps to a different type ID than the one in the frame. Typical when a read loop assumes a fixed response type but the peer sends a different message first (e.g. an error response, a pong instead of a login resp), or when message ordering assumptions break.","commonSituations":"Request/response code that expects exactly one message kind on a connection that multiplexes several; races where a control message arrives between a request and its expected reply; test code reusing one target struct for multiple message types.","solutions":["Use the generic decoder (DecodeV2MessageFrame) or the transport layer's Do() with an expected-response type when order is not guaranteed, then type-switch on the result","Read and dispatch messages by their type ID instead of assuming the next message is a specific type","If an error reply is possible, expect it: type-switch and check for the error/resp variant before asserting the happy-path type","Check the error message text: it names both the actual and expected types, telling you exactly what arrived"],"exampleFix":"// before\nvar pong msg.Pong\n_ = msg.DecodeV2MessageFrameInto(frame, &pong) // fails if server sent LoginResp\n\n// after\nm, err := msg.DecodeV2MessageFrame(frame)\nif err != nil {\n    return err\n}\nswitch v := m.(type) {\ncase *msg.Pong:\n    handlePong(v)\ncase *msg.LoginResp:\n    handleLoginResp(v)\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func asPong(m msg.Message) (*msg.Pong, bool) {\n    p, ok := m.(*msg.Pong)\n    return p, ok\n}","tryCatchPattern":"m, err := msg.DecodeV2MessageFrame(frame)\nif err != nil {\n    return err\n}\nif v, ok := m.(*msg.Ping); ok {\n    return handlePing(v)\n}\nreturn fmt.Errorf(\"unexpected message %T\", m)","preventionTips":["Prefer generic decode + type-switch over decode-into when message order is not guaranteed","Use transport Do() with expected response types for RPC-style exchanges","Handle error/response variants before asserting the happy-path type"],"tags":["go","frp","protocol","type-mismatch","serialization"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}