{"record":{"id":"0f699fb53375be6c","repo":"siyuan-note/siyuan","slug":"invalid-plugin-json-response","errorCode":null,"errorMessage":"invalid plugin JSON response","messagePattern":"invalid plugin JSON response","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/plugin_service_protocol.go","lineNumber":209,"sourceCode":"\t\treturn fmt.Errorf(\"unregistered plugin service: %s %s\", method, path)\n\t}\n\tif err := validatePluginServiceStatus(mode, status); err != nil {\n\t\treturn err\n\t}\n\tif method == \"HEAD\" || status < 200 || status == 204 || status == 304 {\n\t\tif len(payload) != 0 {\n\t\t\treturn fmt.Errorf(\"plugin service response forbids a body\")\n\t\t}\n\t\treturn nil\n\t}\n\tswitch mode {\n\tcase PluginServiceEmpty:\n\t\tif len(payload) != 0 {\n\t\t\treturn fmt.Errorf(\"empty plugin response contains a body\")\n\t\t}\n\tcase PluginServiceJSON, PluginServiceASCIIJSON, PluginServiceIndentedJSON, PluginServicePureJSON:\n\t\tif !json.Valid(payload) {\n\t\t\treturn fmt.Errorf(\"invalid plugin JSON response\")\n\t\t}\n\tcase PluginServiceJSONP:\n\t\tvalid := json.Valid(payload)\n\t\tif tail, ok := strings.CutSuffix(string(payload), \");\"); ok {\n\t\t\tfor index, char := range tail {\n\t\t\t\tif char == '(' && json.Valid([]byte(tail[index+1:])) {\n\t\t\t\t\tvalid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !valid {\n\t\t\treturn fmt.Errorf(\"invalid plugin JSONP response\")\n\t\t}\n\tcase PluginServiceSecureJSON:\n\t\tif !json.Valid(payload) && !json.Valid([]byte(strings.TrimPrefix(string(payload), \"while(1);\"))) {\n\t\t\treturn fmt.Errorf(\"invalid plugin secure JSON response\")\n\t\t}","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/plugin_service_protocol.go#L191-L227","documentation":"ValidatePluginServiceResponse checks that a plugin endpoint declared with a JSON output mode (PluginServiceJSON, ASCIIJSON, IndentedJSON, or PureJSON) actually returns well-formed JSON. When the response payload fails encoding/json's json.Valid check, the validator rejects it with \"invalid plugin JSON response\". This is a contract-enforcement error: the bundle schema promised JSON, but the plugin handler returned bytes that are not parseable JSON.","triggerScenarios":"Calling Bundle.ValidatePluginServiceResponse (directly or via the kernel's plugin service response pipeline) with mode set to one of the four JSON modes and a payload that is not valid JSON, e.g. a Go string rendered verbatim, truncated output, or HTML error text from an upstream.","commonSituations":"A plugin handler writes a Go value with fmt.Fprintf/Fprintf instead of json.Marshal or encoding/json helpers; an upstream proxied body is passed through without re-encoding; the response is truncated by a size limit; the handler emits a BOM or trailing garbage after the JSON value.","solutions":["Encode the response with json.Marshal / json.NewEncoder (or the endpoint's JSON helper) instead of writing raw text","Print the payload and run it through a JSON validator (or json.Valid in a scratch test) to find the syntax error","Check whether upstream bytes are being forwarded for a proxy-style handler; decode-and-reencode them as JSON","Ensure no BOM, log lines, or trailing characters are written around the JSON body"],"exampleFix":"// before\nfmt.Fprintf(w, \"user: %s\", name)\n// after\njson.NewEncoder(w).Encode(map[string]string{\"user\": name})","handlingStrategy":"validation","validationCode":"func isValidPluginJSON(payload []byte) bool { return json.Valid(bytes.TrimSpace(payload)) }","typeGuard":"func isJSONBody(p []byte) bool { return len(bytes.TrimSpace(p)) > 0 && json.Valid(bytes.TrimSpace(p)) }","tryCatchPattern":"if err := bundle.ValidatePluginServiceResponse(method, path, mode, status, contentType, payload); err != nil { if strings.Contains(err.Error(), \"invalid plugin JSON response\") { logPayloadAndDrop(err, payload); return }; return err }","preventionTips":["Always encode responses with json.Marshal or json.NewEncoder, never fmt.Fprintf of raw values","Run json.Valid on payloads in handler tests before shipping","Strip BOM and ensure nothing else writes to the response writer","Add contract tests that exercise each JSON output mode"],"tags":["json","validation","plugin-api","response-format"],"backgroundTag":"invalid-json-response","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"}