siyuan-note/siyuan · error
invalid response format
Error message
invalid response format: %v
What it means
This error is returned by the kernel plugin server's HTTP request bridge (handleHttpRequest). After invoking the plugin's JS fetch handler, the returned JS object is serialized with MarshalJSON and then decoded back into the Go HttpResponse struct; if that JSON cannot be unmarshalled into the expected response shape, the kernel reports 'invalid response format'. It means the plugin handler returned an object that does not match the HttpResponse contract (body, status, headers) in a way the Go side can decode.
Solutions
- Log the exact value returned by the handler before returning it and make it a proper response object (status, headers, body fields)
- Ensure response.status is a number and body follows the expected structure; for binary payloads pass body.raw.data as string|Buffer|ArrayBuffer and let the kernel extract it
- Update the plugin to the current server API contract for HttpResponse (check SiYuan changelog for changes)
- If the error persists, isolate the handler with a minimal {status:200} response and add fields back one at a time to find the offending field
Example fix
// before
async function handler(request) {
return "ok";
}
// after
async function handler(request) {
return { status: 200, headers: { "content-type": "text/plain" }, body: { raw: { data: "ok" } } };
} Defensive patterns
Strategy: validation
Validate before calling
function isValidResponse(res) {
return res && typeof res === "object" && (res.status === undefined || typeof res.status === "number") && (res.body === undefined || typeof res.body === "object");
} Type guard
const isHttpResponse = (v) => !!v && typeof v === "object" && ("status" in v ? typeof v.status === "number" : true) && ("body" in v ? typeof v.body === "object" : true); Try / catch
try { return await handler(request); } catch (e) { console.error("handler returned invalid response", e); return { status: 500, body: { raw: { data: "internal error" } } }; } Prevention
- Always return an object matching the HttpResponse contract (status, headers, body)
- Keep status numeric and body an object; never return strings/arrays from the handler
- Add a unit test that runs the handler through the kernel bridge once per endpoint
- Check the SiYuan changelog when upgrading for HttpResponse contract changes
When it happens
Trigger: A plugin's server handler (fetch-style) returns an object whose fields have types incompatible with the Go HttpResponse struct, or the MarshalJSON output contains values that fail json.Unmarshal into HttpResponse (e.g. non-numeric status, malformed body structure, body.raw.data left in an unconvertible form).
Common situations: Plugin authors returning a plain string or array from the handler instead of a response object; setting response.status to a string; nesting body incorrectly (e.g. body as a string instead of {raw: {...}}); returning values from async code that resolved to undefined-adjacent shapes; plugin API drift after a SiYuan version change.
Related errors
- failed to convert request value to object
- js value cannot be exported to a valid Go value
- unsupported data type: %T
- capability name must not be empty
- config.description is required and must be a string
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/cb3679af3b4553a0.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/plugin.go:969
}
}
// ❌ panic: invalid memory address or nil pointer dereference
// response := HttpResponse{}
// if err := rt.ExportTo(responseObj, &response); err != nil {
// done <- &ServerHandlerResult{Error: fmt.Errorf("invalid response format: %v", err)}
// return
// }
resultJson, marshalErr := responseObj.MarshalJSON()
if marshalErr != nil {
done <- &handleResult{Error: marshalErr}
return
}
response := HttpResponse{}
if unmarshalErr := json.Unmarshal(resultJson, &response); unmarshalErr != nil {
done <- &handleResult{Error: fmt.Errorf("invalid response format: %v", unmarshalErr)}
return
}
if raw != nil && response.Body != nil && response.Body.Raw != nil {
response.Body.Raw.Data = *raw
}
done <- &handleResult{Value: &response}
}, rt, true, handler, handlerObj, jsRequest)
return
}, func(_ *goja.Runtime, _ any, err error) {
if err != nil {
done <- &handleResult{Error: err}
}
})
if runErr != nil {
done <- &handleResult{Error: runErr}
}View on GitHub (pinned to 9f775e8a12)