{"record":{"id":"a093bcd19ea15e1f","repo":"siyuan-note/siyuan","slug":"marshal-ai-editor-actions-failed-w","errorCode":null,"errorMessage":"marshal AI editor actions failed: %w","messagePattern":"marshal AI editor actions failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/ai_editor.go","lineNumber":197,"sourceCode":"\t\t}\n\t\tids[action.ID] = struct{}{}\n\t}\n\treturn ret, nil\n}\n\nfunc saveAIEditorActions(data *aiEditorActionsData) (err error) {\n\tdata.Version = aiEditorActionsVersion\n\tif data.Actions == nil {\n\t\tdata.Actions = []*AIEditorAction{}\n\t}\n\n\tdirPath := filepath.Dir(aiEditorActionsPath())\n\tif err = os.MkdirAll(dirPath, 0755); err != nil {\n\t\treturn fmt.Errorf(\"create AI editor actions directory failed: %w\", err)\n\t}\n\tbytes, err := gulu.JSON.MarshalIndentJSON(data, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal AI editor actions failed: %w\", err)\n\t}\n\tif err = filelock.WriteFile(aiEditorActionsPath(), bytes); err != nil {\n\t\treturn fmt.Errorf(\"write AI editor actions failed: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc migrateLegacyAIEditorActions(data *aiEditorActionsData, persist bool) (err error) {\n\tlocalStorage := GetLocalStorage()\n\tlegacyRaw, ok := localStorage[legacyAIEditorActionsStorageKey]\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tvar legacyActions []*legacyAIEditorAction\n\tif legacyJSON, isString := legacyRaw.(string); isString {\n\t\tif err = gulu.JSON.UnmarshalJSON([]byte(legacyJSON), &legacyActions); err != nil {\n\t\t\treturn fmt.Errorf(\"unmarshal legacy AI editor actions failed: %w\", err)","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/ai_editor.go#L179-L215","documentation":"saveAIEditorActions marshals the aiEditorActionsData struct to indented JSON with gulu.JSON.MarshalIndentJSON before writing. This error wraps any failure from that marshal step. In practice Go's encoding/json only fails for values it cannot represent (e.g. unsupported types like channels or funcs reachable through the struct), so this is rare and points to an internal invariant rather than user input.","triggerScenarios":"SaveAIEditorAction, RemoveAIEditorAction, or migrateLegacyAIEditorActions calls saveAIEditorActions with a data value whose marshaling fails — essentially only if AIEditorAction/aiEditorActionsData gained a field of an unsupported type, or a corrupted in-memory action value cannot be encoded.","commonSituations":"A custom kernel build or patched fork added a field to AIEditorAction with an unsupported type (chan, func, complex); a nil-bearing data structure from a broken caller path defeats encoding assumptions.","solutions":["Read the wrapped error to identify the offending field/type reported by encoding/json","Ensure every field in aiEditorActionsData/AIEditorAction is of JSON-encodable types (string, int, slices, pointers)","Verify no third-party patch changed the struct; revert to upstream struct definitions","If caused by in-memory corruption, restart the kernel so the store is reloaded from disk"],"exampleFix":"// before: a patched struct with an unsupported field\ntype AIEditorAction struct {\n    ID     string `json:\"id\"`\n    Hook   func() `json:\"hook\"` // not JSON-encodable\n}\n// after: keep only encodable fields\ntype AIEditorAction struct {\n    ID     string `json:\"id\"`\n    Name   string `json:\"name\"`\n    Action string `json:\"action\"`\n}","handlingStrategy":"try-catch","validationCode":"// sanity-check the in-memory value encodes before persisting\nif _, err := json.Marshal(data); err != nil {\n    return fmt.Errorf(\"in-memory actions not encodable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := SaveAIEditorAction(act); err != nil && strings.Contains(err.Error(), \"marshal AI editor actions failed\") {\n    return fmt.Errorf(\"internal encoding failure, restart the kernel: %w\", err)\n}","preventionTips":["Keep AIEditorAction fields limited to JSON-encodable types when forking/patching","Restart the kernel after applying custom patches that touch the storage structs","Do not inject runtime values (funcs/channels) into persisted structures"],"tags":["json","serialization","internal","ai-editor"],"backgroundTag":"json-marshal-failed","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"}