siyuan-note/siyuan · error
handler did not return an object
Error message
handler did not return an object
What it means
In handleHttpRequest, after the JS handler returns, the kernel converts result.Value to an object; if ToObject yields nil (the handler returned undefined/null or a primitive that cannot become an object in a meaningful way), the kernel reports `handler did not return an object` as the handleResult error. HTTP handling expects a response-shaped object with fields like status/body.
Solutions
- Make the handler always return a response object on every code path, e.g. `{ status: 200, body: ... }`
- Ensure the Promise passed to invokeFunction resolves to the response, not to a side-effect-only call
- Add a JS-side guard: if the computed response is falsy, return a default 500 response object
- Check early-return branches in the handler for missing return values
Example fix
// before
async handle(req) {
if (!req.path) return // falls through -> undefined
return { status: 200, body: 'ok' }
}
// after
async handle(req) {
if (!req.path) return { status: 400, body: 'missing path' }
return { status: 200, body: 'ok' }
} Defensive patterns
Strategy: validation
Validate before calling
// plugin JS: validate the response before returning it
function makeResponse(status, body) {
if (typeof status !== 'number') status = 500
if (body === undefined || body === null) body = ''
return { status: status, body: body }
} Type guard
// plugin JS
function isResponseObject(v) {
return v !== null && typeof v === 'object' && typeof v.status === 'number'
} Try / catch
// kernel caller side
res := p.handleHttpRequest(req)
if res.Error != nil && res.Error.Error() == "handler did not return an object" {
http.Error(w, "plugin handler returned no response", http.StatusBadGateway)
} Prevention
- Always return an explicit response object from every branch of the handler
- Wrap handler bodies so the final result is `{status, body}` even on error
- Never return the result of a void helper; build the response object explicitly
- Unit-test handlers with early-return inputs (missing fields, bad methods)
When it happens
Trigger: A plugin HTTP-request handler that returns nothing (falls off the end), returns undefined/null explicitly, or returns a Promise resolved to undefined instead of a response object.
Common situations: Handler with early-return paths that skip building the response; awaiting a helper that returns undefined on error; plugin returning the result of a void function instead of a `{status, body}` object.
Related errors
- failed to serialize config.
- failed to serialize inputSchema
- globalThis.siyuan.plugin.lifecycle is not an object
- globalThis.siyuan.plugin.lifecycle not found
- globalThis.siyuan.plugin.lifecycle.
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/c15e9ce5b922907d.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/plugin.go:928
err = getHandlerErr
return
}
jsRequest, convertErr := requestGoToJs(p, rt, request)
if convertErr != nil {
err = convertErr
return
}
invokeFunction(func(rt *goja.Runtime, result *CallResult) {
if result.Error != nil {
done <- &handleResult{Error: result.Error}
return
}
responseObj := result.Value.ToObject(rt)
if responseObj == nil {
done <- &handleResult{Error: fmt.Errorf("handler did not return an object")}
return
}
// convert response.body?.raw?.data from (string | Buffer | ArrayBuffer) to []byte
var raw *[]byte
if bodyValue := responseObj.Get("body"); isJsValueNotNull(bodyValue) {
// response.body
if bodyObj := bodyValue.ToObject(rt); bodyObj != nil {
if rawValue := bodyObj.Get("raw"); isJsValueNotNull(rawValue) {
// response.body.raw
if rawObj := rawValue.ToObject(rt); rawObj != nil {
if dataValue := rawObj.Get("data"); isJsValueNotNull(dataValue) {
// response.body.raw.data
dataBytes, convertErr := jsValueToBytes(rt, dataValue)
if convertErr == nil {
raw = &dataBytes
rawObj.Set("data", goja.Null())
}View on GitHub (pinned to 8641553a1f)