{"record":{"id":"7100d2e49be5c440","repo":"siyuan-note/siyuan","slug":"rpc-reply-requires-a-response-or-error","errorCode":null,"errorMessage":"RPC reply requires a response or error","messagePattern":"RPC reply requires a response or error","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/rpc_contract.go","lineNumber":159,"sourceCode":"\tif response.Response != nil {\n\t\tvalue := response.Response\n\t\tresult, err := json.Marshal(value.Result)\n\t\tif err != nil {\n\t\t\treturn apicontract.PluginRPCReply{}, err\n\t\t}\n\t\tencoded, err := apicontract.EncodedJSONValue(result)\n\t\tif err != nil {\n\t\t\treturn apicontract.PluginRPCReply{}, err\n\t\t}\n\t\tid, err := pluginRPCID(value.ID)\n\t\tif err != nil {\n\t\t\treturn apicontract.PluginRPCReply{}, err\n\t\t}\n\t\treturn apicontract.RPCSuccessReply(apicontract.PluginRPCSuccess{JSONRPC: value.JsonRpc, Result: encoded, ID: id}), nil\n\t}\n\tvalue := response.Error\n\tif value == nil {\n\t\treturn apicontract.PluginRPCReply{}, fmt.Errorf(\"RPC reply requires a response or error\")\n\t}\n\tid, err := pluginRPCID(value.ID)\n\tif err != nil {\n\t\treturn apicontract.PluginRPCReply{}, err\n\t}\n\tresult := apicontract.PluginRPCFailure{JSONRPC: value.JsonRpc, ID: id}\n\tif value.Error != nil {\n\t\tresult.Error = &apicontract.PluginRPCError{Code: int(value.Error.Code), Message: value.Error.Message}\n\t\tif value.Error.Data != nil {\n\t\t\tdata, err := json.Marshal(value.Error.Data)\n\t\t\tif err != nil {\n\t\t\t\treturn apicontract.PluginRPCReply{}, err\n\t\t\t}\n\t\t\tencoded, err := apicontract.EncodedJSONValue(data)\n\t\t\tif err != nil {\n\t\t\t\treturn apicontract.PluginRPCReply{}, err\n\t\t\t}\n\t\t\tresult.Error.Data = &encoded","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/rpc_contract.go#L141-L177","documentation":"pluginRPCReply converts an internal JsonRpcProcessingResponse into an apicontract.PluginRPCReply. A response must carry either a Result (success) or an Error; if response.Error is nil after the success path was not taken, the reply is unusable and this error is thrown.","triggerScenarios":"Calling pluginRPCReply with a JsonRpcProcessingResponse whose Result was not encodable into a success reply and whose Error field is nil — e.g. a zero-value response struct, or a handler that returned neither a result nor an error.","commonSituations":"Constructing JsonRpcProcessingResponse manually in tests; a dispatch path that forgets to set response.Error when the plugin call fails without producing a result; type changes in the response struct leaving fields unset.","solutions":["Set response.Error to a JsonRpcProcessingError whenever the call did not succeed","Fix the dispatch path so every response is either RPCSuccessReply or carries a populated Error","Check that the success encoding path did not silently drop the response before reaching the error branch","Add a pre-check validating Result-or-Error presence before calling pluginRPCReply"],"exampleFix":"// before\nresp := JsonRpcProcessingResponse{}\nreply, err := pluginRPCReply(resp)\n// after\nresp := JsonRpcProcessingResponse{Error: &JsonRpcProcessingError{Code: -32603, Message: \"internal error\"}}\nreply, err := pluginRPCReply(resp)","handlingStrategy":"validation","validationCode":"if resp.Result == nil && resp.Error == nil {\n    return fmt.Errorf(\"response must carry result or error\")\n}","typeGuard":"func replyable(r JsonRpcProcessingResponse) bool { return r.Error != nil }","tryCatchPattern":"reply, err := pluginRPCReply(resp)\nif err != nil {\n    reply = apicontract.RPCFailureReply(id, -32603, err.Error())\n}","preventionTips":["Set Error on every non-successful response before calling pluginRPCReply","Never construct zero-value JsonRpcProcessingResponse structs","Cover both success and failure reply paths in unit tests"],"tags":["rpc","json-rpc","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}