{"record":{"id":"2d6a2508f7cb369a","repo":"wavetermdev/waveterm","slug":"request-packets-may-not-have-both-reqid-and-resid","errorCode":null,"errorMessage":"request packets may not have both reqid and resid set","messagePattern":"request packets may not have both reqid and resid set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wshutil/wshrpc.go","lineNumber":147,"sourceCode":"\tReqId    string `json:\"reqid,omitempty\"`\n\tResId    string `json:\"resid,omitempty\"`\n\tTimeout  int64  `json:\"timeout,omitempty\"`\n\tRoute    string `json:\"route,omitempty\"`  // to route/forward requests to alternate servers\n\tSource   string `json:\"source,omitempty\"` // source route id\n\tCont     bool   `json:\"cont,omitempty\"`   // flag if additional requests/responses are forthcoming\n\tCancel   bool   `json:\"cancel,omitempty\"` // used to cancel a streaming request or response (sent from the side that is not streaming)\n\tError    string `json:\"error,omitempty\"`\n\tDataType string `json:\"datatype,omitempty\"`\n\tData     any    `json:\"data,omitempty\"`\n}\n\nfunc (r *RpcMessage) IsRpcRequest() bool {\n\treturn r.Command != \"\" || r.ReqId != \"\"\n}\n\nfunc (r *RpcMessage) Validate() error {\n\tif r.ReqId != \"\" && r.ResId != \"\" {\n\t\treturn fmt.Errorf(\"request packets may not have both reqid and resid set\")\n\t}\n\tif r.Cancel {\n\t\tif r.Command != \"\" {\n\t\t\treturn fmt.Errorf(\"cancel packets may not have command set\")\n\t\t}\n\t\tif r.ReqId == \"\" && r.ResId == \"\" {\n\t\t\treturn fmt.Errorf(\"cancel packets must have reqid or resid set\")\n\t\t}\n\t\tif r.Data != nil {\n\t\t\treturn fmt.Errorf(\"cancel packets may not have data set\")\n\t\t}\n\t\treturn nil\n\t}\n\tif r.Command != \"\" {\n\t\tif r.ResId != \"\" {\n\t\t\treturn fmt.Errorf(\"command packets may not have resid set\")\n\t\t}\n\t\tif r.Error != \"\" {","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wshutil/wshrpc.go#L129-L165","documentation":"RpcMessage.Validate enforces that a message is either a request (ReqId) or a response (ResId), never both. A message with both is ambiguous — the router could not tell if it is a new request or a reply. This is a protocol sanity check on inbound/outbound packets.","triggerScenarios":"Calling Validate on an RpcMessage where both ReqId and ResId are non-empty strings.","commonSituations":"A producer accidentally copies ResId into a new request; a relay/gateway that mutates responses and re-sends them as requests; corrupted or hand-crafted JSON messages injected into the wire.","solutions":["Clear ResId if the message is a request (r.ResId = \"\")","Clear ReqId if the message is actually a response (r.ReqId = \"\")","Fix the sender code so responses never carry ReqId and vice versa","Validate messages at construction time with Validate() before sending"],"exampleFix":"// before\nmsg := wshutil.RpcMessage{ReqId: \"r1\", ResId: \"res1\"}\n// after\nmsg := wshutil.RpcMessage{ReqId: \"r1\"} // request only","handlingStrategy":"validation","validationCode":"if msg.ReqId != \"\" && msg.ResId != \"\" {\n\tmsg.ResId = \"\" // treat as request\n}\nif err := msg.Validate(); err != nil { return err }","typeGuard":"func isPureRequest(msg wshutil.RpcMessage) bool {\n\treturn msg.ReqId != \"\" && msg.ResId == \"\"\n}","tryCatchPattern":"if err := msg.Validate(); err != nil {\n\treturn fmt.Errorf(\"dropping malformed rpc packet: %w\", err)\n}","preventionTips":["Build requests and responses with separate constructors","Never mutate a response message into a request in place","Call Validate() before every send in development builds"],"tags":["wsh","rpc","validation","protocol"],"backgroundTag":"invalid-rpc-packet","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}