{"record":{"id":"08bddeca568967b1","repo":"siyuan-note/siyuan","slug":"js-value-cannot-be-exported-to-a-valid-go-value","errorCode":null,"errorMessage":"js value cannot be exported to a valid Go value","messagePattern":"js value cannot be exported to a valid Go value","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/sandbox.go","lineNumber":489,"sourceCode":"\n// jsValueToBytes attempts to convert a goja.Value to a byte slice, supporting string, Buffer, ArrayBuffer, etc.\nfunc jsValueToBytes(rt *goja.Runtime, value goja.Value) (data []byte, err error) {\n\tif goValue := value.Export(); goValue != nil {\n\t\tswitch d := goValue.(type) {\n\t\tcase string: // string\n\t\t\tdata = []byte(d)\n\t\tcase []byte: // Buffer\n\t\t\tdata = d\n\t\tcase goja.ArrayBuffer: // ArrayBuffer\n\t\t\tdata = d.Bytes()\n\t\tcase buffer.Buffer: // ?\n\t\t\tdata = buffer.Bytes(rt, value)\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"unsupported data type: %T\", goValue)\n\t\t}\n\t\treturn\n\t}\n\terr = fmt.Errorf(\"js value cannot be exported to a valid Go value\")\n\treturn\n}\n\n// getRequestHandler retrieves the handler function and its containing object for a given scope and request type from the plugin's JS context.\nfunc getRequestHandler(rt *goja.Runtime, scope AccessScope, requestType RequestType) (handler goja.Callable, handlerObj *goja.Object, err error) {\n\t// Get handler object: siyuan.server[scope][requestType]\n\thandlerObjValue, getObjErr := getJsContextValue(rt, []any{\"siyuan\", \"server\", string(scope), string(requestType)})\n\tif getObjErr != nil {\n\t\terr = getObjErr\n\t\treturn\n\t}\n\n\thandlerObj = handlerObjValue.ToObject(rt)\n\tif handlerObj == nil {\n\t\terr = fmt.Errorf(\"globalThis.siyuan.server[%s][%s] is not an object\", scope, requestType)\n\t\treturn\n\t}\n","sourceCodeStart":471,"sourceCodeEnd":507,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/plugin/sandbox.go#L471-L507","documentation":"jsValueToBytes in the plugin sandbox converts a JavaScript value returned by a plugin handler into raw Go bytes. After checking all known JS types (e.g. ArrayBuffer, typed arrays, strings via buffer), none matched the runtime type of the value, so the bridge gives up rather than guessing a serialization. It signals a type contract violation: the plugin returned a JS value the Go side cannot represent as bytes.","triggerScenarios":"A plugin request handler registered via globalThis.siyuan.server[scope][requestType].handler resolves/returns a value that is not an ArrayBuffer, typed array, or string (e.g. a plain object, undefined, null, a Promise resolving to an object, or a number), and the Go sandbox calls jsValueToBytes on it.","commonSituations":"Plugin authors returning JSON objects instead of binary/string bodies from a fetch-like handler; forgetting `await` so the handler returns a Promise; returning undefined from an early-exit code path; a plugin updated to a new response shape that the kernel version doesn't know.","solutions":["Fix the plugin handler to return an ArrayBuffer, a typed array (Uint8Array etc.), or a string as its response body.","If returning a Promise, await it inside the handler so the exported value is the resolved type.","Serialize the object in JS first, e.g. `return JSON.stringify(obj)` or `new TextEncoder().encode(JSON.stringify(obj))`.","Check kernel/plugin/sandbox.go jsValueToBytes for the list of supported types and add a conversion case if a new JS type must be supported (kernel-side change)."],"exampleFix":"// before (plugin JS)\nhandler: (req) => ({ ok: true, data: \"...\" });\n// after\nhandler: (req) => JSON.stringify({ ok: true, data: \"...\" });","handlingStrategy":"type-guard","validationCode":"function isExportableBody(v) {\n  return v instanceof ArrayBuffer || ArrayBuffer.isView(v) || typeof v === \"string\";\n}\nconst body = await handler(req);\nif (!isExportableBody(body)) throw new TypeError(\"handler must return ArrayBuffer, typed array, or string\");","typeGuard":"function isExportableBody(v) {\n  return v instanceof ArrayBuffer || ArrayBuffer.isView(v) || typeof v === \"string\";\n}","tryCatchPattern":null,"preventionTips":["Always return string, ArrayBuffer, or typed arrays from plugin handlers.","Await Promises inside the handler before returning.","Serialize objects with JSON.stringify before returning them."],"tags":["goja","javascript-bridge","type-conversion","plugin"],"backgroundTag":"incompatible-source-type","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}