siyuan-note/siyuan · error

empty plugin response contains a body

Error message

empty plugin response contains a body

What it means

Empty-mode plugin service responses must have zero-length payloads. This error means the handler declared an empty response (PluginServiceEmpty) but the validated payload contains bytes, contradicting the declared variant.

Solutions

  1. Remove the body write so the handler emits nothing for empty-mode responses
  2. If a body is intended, declare the response with the matching variant (e.g. PluginServiceJSON or PluginServiceString)
  3. In tests, pass nil/empty payload when validating empty-mode responses

Example fix

// before
respond(PluginServiceEmpty, 200, []byte("ok"))
// after
respond(PluginServiceEmpty, 200, nil)
Defensive patterns

Strategy: validation

Validate before calling

if mode == apicontract.PluginServiceEmpty && len(payload) != 0 {
	payload = nil
}

Prevention

When it happens

Trigger: Calling Bundle.ValidatePluginServiceResponse with mode PluginServiceEmpty and a non-empty payload for a non-HEAD, non-204/304 status; a handler that uses the empty responder but still writes data.

Common situations: Switching a handler from JSON to empty responses without removing the final c.JSON/body write; middleware appending bytes after the handler; test fixtures that reuse a populated body for an empty-mode case.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/ba9a0aaba5689bb6. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/plugin_service_protocol.go:205

			break
		}
	}
	if !found {
		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")
		}

View on GitHub (pinned to 9f775e8a12)