siyuan-note/siyuan · error
invalid plugin JSON response
Error message
invalid plugin JSON response
What it means
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.
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
Example fix
// before
fmt.Fprintf(w, "user: %s", name)
// after
json.NewEncoder(w).Encode(map[string]string{"user": name}) Defensive patterns
Strategy: validation
Validate before calling
func isValidPluginJSON(payload []byte) bool { return json.Valid(bytes.TrimSpace(payload)) } Type guard
func isJSONBody(p []byte) bool { return len(bytes.TrimSpace(p)) > 0 && json.Valid(bytes.TrimSpace(p)) } Try / catch
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 } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- invalid plugin JSONP response
- invalid plugin secure JSON response
- builtin color must not be null
- builtin style must not be null
- Conf.Language(317)
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/0f699fb53375be6c.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/plugin_service_protocol.go:209
return fmt.Errorf("unregistered plugin service: %s %s", method, path)
}
if err := validatePluginServiceStatus(mode, status); err != nil {
return err
}
if method == "HEAD" || status < 200 || status == 204 || status == 304 {
if len(payload) != 0 {
return fmt.Errorf("plugin service response forbids a body")
}
return nil
}
switch mode {
case PluginServiceEmpty:
if len(payload) != 0 {
return fmt.Errorf("empty plugin response contains a body")
}
case PluginServiceJSON, PluginServiceASCIIJSON, PluginServiceIndentedJSON, PluginServicePureJSON:
if !json.Valid(payload) {
return fmt.Errorf("invalid plugin JSON response")
}
case PluginServiceJSONP:
valid := json.Valid(payload)
if tail, ok := strings.CutSuffix(string(payload), ");"); ok {
for index, char := range tail {
if char == '(' && json.Valid([]byte(tail[index+1:])) {
valid = true
break
}
}
}
if !valid {
return fmt.Errorf("invalid plugin JSONP response")
}
case PluginServiceSecureJSON:
if !json.Valid(payload) && !json.Valid([]byte(strings.TrimPrefix(string(payload), "while(1);"))) {
return fmt.Errorf("invalid plugin secure JSON response")
}View on GitHub (pinned to 9f775e8a12)