{"record":{"id":"b075aa5c8a7c0dac","repo":"siyuan-note/siyuan","slug":"ai-editor-action-not-found","errorCode":null,"errorMessage":"AI editor action not found","messagePattern":"AI editor action not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/ai_editor.go","lineNumber":107,"sourceCode":"\n\tret = &AIEditorAction{\n\t\tID:     action.ID,\n\t\tName:   action.Name,\n\t\tAction: action.Action,\n\t}\n\tif ret.ID == \"\" {\n\t\tret.ID = ast.NewNodeID()\n\t\tdata.Actions = append(data.Actions, ret)\n\t} else {\n\t\tupdated := false\n\t\tfor i, item := range data.Actions {\n\t\t\tif item.ID == ret.ID {\n\t\t\t\tdata.Actions[i] = ret\n\t\t\t\tupdated = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !updated {\n\t\t\treturn nil, errors.New(\"AI editor action not found\")\n\t\t}\n\t}\n\n\tif err = saveAIEditorActions(data); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ret, nil\n}\n\nfunc RemoveAIEditorAction(id string) (err error) {\n\tif !ast.IsNodeIDPattern(id) {\n\t\treturn errors.New(\"invalid AI editor action ID\")\n\t}\n\n\twaitForSyncingStorages()\n\taiEditorActionsLock.Lock()\n\tdefer aiEditorActionsLock.Unlock()","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/ai_editor.go#L89-L125","documentation":"When SaveAIEditorAction is called with a non-empty ID, it treats the call as an update: it searches the stored actions for a matching ID and replaces it. If no stored action has that ID, `updated` stays false and SiYuan returns \"AI editor action not found\" — you cannot 'update' an action that was never created (or was deleted).","triggerScenarios":"Calling SaveAIEditorAction with a well-formed but nonexistent ID; updating an action that was removed in another session or another synced device; stale IDs cached by a plugin after the action list changed.","commonSituations":"Race between two clients where one deletes the action while another edits it; plugin holding a stale snapshot of the action list; typo'd ID that happens to match the node-ID pattern.","solutions":["List current actions (GetAIEditorActions) and confirm the ID exists before updating","If creating is intended, omit the ID so the kernel appends a new action","Refresh the client's cached action list after deletes/sync to avoid stale IDs","Handle the error by falling back to a create (save without ID) if appropriate"],"exampleFix":"// before\nsaveAIEditorAction(&AIEditorAction{ID: staleID, Name: \"Renamed\"})\n// after\nactions, _ := GetAIEditorActions()\nif !containsID(actions, staleID) { return saveAIEditorAction(&AIEditorAction{Name: \"Renamed\"}) } // create instead","handlingStrategy":"validation","validationCode":"const actions = await fetchPost(\"/api/ai/editor/action/list\", {});\nif (!actions.some(a => a.id === id)) throw new Error(\"Action no longer exists; refresh the list\");","typeGuard":null,"tryCatchPattern":"try {\n  await saveAIEditorAction({ id, ...changes });\n} catch (e) {\n  if (e.message.includes(\"not found\")) {\n    // refresh action list; offer to recreate the action\n  }\n}","preventionTips":["Refresh the cached action list before updates","Re-list after deletes and after sync completes","Distinguish create (no ID) from update (existing ID) in client code"],"tags":["api","not-found","ai"],"backgroundTag":"entity-not-found","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"}