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

  1. Log the exact value returned by the handler before returning it and make it a proper response object (status, headers, body fields)
  2. 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
  3. Update the plugin to the current server API contract for HttpResponse (check SiYuan changelog for changes)
  4. 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

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


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)