{"record":{"id":"eadb9d956f35686b","repo":"wavetermdev/waveterm","slug":"non-command-request-packets-may-not-have-timeout-s","errorCode":null,"errorMessage":"non-command request packets may not have timeout set","messagePattern":"non-command request packets may not have timeout set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wshutil/wshrpc.go","lineNumber":178,"sourceCode":"\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}\n\tif r.ReqId != \"\" {\n\t\tif r.ResId == \"\" {\n\t\t\treturn fmt.Errorf(\"request packets must have resid set\")\n\t\t}\n\t\tif r.Timeout != 0 {\n\t\t\treturn fmt.Errorf(\"non-command request packets may not have timeout set\")\n\t\t}\n\t\treturn nil\n\t}\n\tif r.ResId != \"\" {\n\t\tif r.Command != \"\" {\n\t\t\treturn fmt.Errorf(\"response packets may not have command set\")\n\t\t}\n\t\tif r.ReqId == \"\" {\n\t\t\treturn fmt.Errorf(\"response packets must have reqid set\")\n\t\t}\n\t\tif r.Timeout != 0 {\n\t\t\treturn fmt.Errorf(\"response packets may not have timeout set\")\n\t\t}\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"invalid packet: must have command, reqid, or resid set\")\n}\n","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wshutil/wshrpc.go#L160-L196","documentation":"Timeout (deadline negotiation) is specified only on the initial command packet of an RPC. Follow-up request packets (ReqId without Command) may not set Timeout; Validate rejects it to avoid ambiguous deadline resets.","triggerScenarios":"Calling Validate on an RpcMessage with ReqId set, ResId set, Command empty, and Timeout != 0.","commonSituations":"A helper that copies all fields of the original command into continuation packets, carrying Timeout along; retries implemented by re-sending with a fresh Timeout on the same ReqId.","solutions":["Set Timeout to 0 on follow-up request packets","Implement per-call timeout changes by canceling and re-issuing a new command with a new ReqId","When cloning the original message for continuations, strip Timeout explicitly"],"exampleFix":"// before\nmsg := wshutil.RpcMessage{ReqId: reqId, ResId: resId, Timeout: origTimeout}\n// after\nmsg := wshutil.RpcMessage{ReqId: reqId, ResId: resId, Timeout: 0}","handlingStrategy":"validation","validationCode":"if msg.ReqId != \"\" && msg.Command == \"\" && msg.Timeout != 0 {\n\tmsg.Timeout = 0\n}\nif err := msg.Validate(); err != nil { return err }","typeGuard":"func followUpHasNoTimeout(msg wshutil.RpcMessage) bool {\n\treturn msg.Command != \"\" || msg.Timeout == 0\n}","tryCatchPattern":"if err := msg.Validate(); err != nil {\n\treturn fmt.Errorf(\"follow-up request has timeout: %w\", err)\n}","preventionTips":["Only set Timeout on the initial command packet","When cloning messages for continuations, strip Timeout and Command","Use cancel + re-issue for changing deadlines instead of re-sending with a new Timeout"],"tags":["wsh","rpc","validation","timeout"],"backgroundTag":"invalid-rpc-packet","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}