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
- Remove the body write so the handler emits nothing for empty-mode responses
- If a body is intended, declare the response with the matching variant (e.g. PluginServiceJSON or PluginServiceString)
- 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 declaring empty responses, remove all body writes including middleware-added ones
- Map each handler to exactly one response variant and keep payload construction consistent
- Add tests asserting zero-length bodies for empty-mode endpoints
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
- invalid plugin redirect status
- invalid plugin SSE status
- invalid plugin WebSocket status
- plugin service response forbids a body
- undeclared plugin admission status
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)