{"record":{"id":"d582e603c2dca84b","repo":"wavetermdev/waveterm","slug":"cancel-packets-must-have-reqid-or-resid-set","errorCode":null,"errorMessage":"cancel packets must have reqid or resid set","messagePattern":"cancel packets must have reqid or resid set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wshutil/wshrpc.go","lineNumber":154,"sourceCode":"\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 != \"\" {\n\t\t\treturn fmt.Errorf(\"command packets may not have error set\")\n\t\t}\n\t\tif r.DataType != \"\" {\n\t\t\treturn fmt.Errorf(\"command packets may not have datatype set\")\n\t\t}\n\t\treturn nil\n\t}","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wshutil/wshrpc.go#L136-L172","documentation":"A cancel packet must identify which message it cancels via ReqId (cancel my request) or ResId (cancel the response/handler). With neither, the cancel is a no-op target-less packet, so Validate rejects it.","triggerScenarios":"Calling Validate on an RpcMessage with Cancel=true, Command empty, and both ReqId and ResId empty.","commonSituations":"Cancelling a request before its ReqId was assigned (cancel called before SendRequest returned); a helper that constructs cancel messages from a zero-value message struct.","solutions":["Populate ReqId with the original request's id when canceling an outgoing request","Populate ResId when canceling an in-flight response on the responder side","Only invoke cancel after the request id is known/assigned"],"exampleFix":"// before\nmsg := wshutil.RpcMessage{Cancel: true}\n// after\nmsg := wshutil.RpcMessage{Cancel: true, ReqId: pendingReqId}","handlingStrategy":"validation","validationCode":"if msg.Cancel && msg.ReqId == \"\" && msg.ResId == \"\" {\n\treturn fmt.Errorf(\"cancel needs reqid or resid\")\n}\nif err := msg.Validate(); err != nil { return err }","typeGuard":"func cancelHasTarget(msg wshutil.RpcMessage) bool {\n\treturn msg.ReqId != \"\" || msg.ResId != \"\"\n}","tryCatchPattern":"if err := msg.Validate(); err != nil {\n\treturn fmt.Errorf(\"cancel packet missing target: %w\", err)\n}","preventionTips":["Only cancel after the request id has been assigned","Track in-flight request ids in a map so cancels always have a target","Guard against canceling zero-value message structs"],"tags":["wsh","rpc","validation","cancel"],"backgroundTag":"invalid-rpc-packet","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}