{"record":{"id":"7b98a7670c0e5d6b","repo":"siyuan-note/siyuan","slug":"failed-to-serialize-config-s-v","errorCode":null,"errorMessage":"failed to serialize config.%s: %v","messagePattern":"failed to serialize config\\.(.+?): (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_agent.go","lineNumber":302,"sourceCode":"}\n\nfunc jsCapabilityActionEffectsToGoEffects(rt *goja.Runtime, value goja.Value) (map[string]tools.ToolEffects, error) {\n\tactionEffects := map[string]tools.ToolEffects{}\n\tif err := unmarshalCapabilityJSON(rt, value, &actionEffects, \"actionEffects\"); err != nil {\n\t\treturn nil, err\n\t}\n\tfor action := range actionEffects {\n\t\tif strings.TrimSpace(action) == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"config.actionEffects contains an empty action\")\n\t\t}\n\t}\n\treturn actionEffects, nil\n}\n\nfunc unmarshalCapabilityJSON(rt *goja.Runtime, value goja.Value, target any, field string) error {\n\tjsonValue, err := value.ToObject(rt).MarshalJSON()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to serialize config.%s: %v\", field, err)\n\t}\n\tif err = json.Unmarshal(jsonValue, target); err != nil {\n\t\treturn fmt.Errorf(\"invalid config.%s: %v\", field, err)\n\t}\n\treturn nil\n}\n","sourceCodeStart":284,"sourceCodeEnd":309,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/api_agent.go#L284-L309","documentation":"unmarshalCapabilityJSON serializes a JS capability config field (identified by name, e.g. config.effects or config.actionEffects) to JSON before decoding it into a Go struct. When goja's MarshalJSON fails, the error names the field. This means the JS value for that config field cannot be represented as JSON at all.","triggerScenarios":"Passing a non-serializable value as config.effects or config.actionEffects: circular references, functions as values, Symbol keys, or a throwing getter somewhere in the object graph.","commonSituations":"Plugin authors embed callback functions inside the effects object expecting them to be invoked, or accidentally share a circular config object between plugins; goja marshalling chokes on it.","solutions":["Ensure the named config field is a plain JSON object (string keys, serializable values)","Remove functions, Symbols, and circular references from the object","Sanitize in JS: JSON.parse(JSON.stringify(value)) before passing it","Read the wrapped error to locate the offending value"],"exampleFix":"// before\nconfig.actionEffects = { run: { handler: (args) => doSomething() } } // function not serializable\n\n// after\nconfig.actionEffects = { run: { readOnlyHint: false, destructiveHint: true } };","handlingStrategy":"validation","validationCode":"function isJsonSerializable(v) {\n  try { JSON.stringify(v); return true; } catch { return false; }\n}\nif (!isJsonSerializable(config.effects)) throw new Error(\"config.effects must be JSON-serializable\");","typeGuard":null,"tryCatchPattern":"try {\n  registerCapability(config);\n} catch (e) {\n  if (String(e).includes(\"failed to serialize config.\")) {\n    console.error(\"Capability config contains non-serializable values\", e);\n  }\n}","preventionTips":["Keep capability config free of functions and circular references","Sanitize with JSON.parse(JSON.stringify(...)) before registering","Keep config builders pure — no getters with side effects"],"tags":["plugin","goja","json","config"],"backgroundTag":"json-marshal-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}