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

  1. Encode the response with json.Marshal / json.NewEncoder (or the endpoint's JSON helper) instead of writing raw text
  2. Print the payload and run it through a JSON validator (or json.Valid in a scratch test) to find the syntax error
  3. Check whether upstream bytes are being forwarded for a proxy-style handler; decode-and-reencode them as JSON
  4. 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

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


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)