{"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":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/ai_editor.go","lineNumber":108,"sourceCode":"\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()\n","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/ai_editor.go#L90-L126","documentation":"SaveAIEditorAction performs an update when the action has an ID: it searches the stored action list for a matching ID and replaces it. If no stored action has that ID, the update target is missing and this error is returned instead of silently creating a duplicate.","triggerScenarios":"Calling SaveAIEditorAction with an ID that is syntactically valid but does not exist in the persisted actions list (action deleted elsewhere, stale client cache, wrong workspace).","commonSituations":"Two clients/sessions where one deleted the action; client retrying an update after the action was removed; pointing the kernel at a different workspace than the one holding the action.","solutions":["Re-fetch the current action list (getAIEditorActions) and retry with a live ID","If the action was intentionally new, omit the ID to create it instead of updating","Confirm the kernel is running against the workspace that contains the action"],"exampleFix":"// before\nawait fetchPost('/api/ai/saveAIEditorAction', {id: staleID, name: 'X', action: 'Y'})\n// after\nconst {data} = await fetchPost('/api/ai/getAIEditorActions', {})\nif (data.actions.some(a => a.id === staleID)) {\n    await fetchPost('/api/ai/saveAIEditorAction', {id: staleID, name: 'X', action: 'Y'})\n}","handlingStrategy":"try-catch","validationCode":"const exists = (await fetchPost('/api/ai/getAIEditorActions', {})).data.actions.some(a => a.id === id); if (!exists) { /* create without id instead */ }","typeGuard":null,"tryCatchPattern":"try { await saveAction({id, ...}) } catch (e) { if (e.message === 'AI editor action not found') { await refreshActions(); /* recreate or surface conflict */ } }","preventionTips":["Refresh the action list before updates in multi-session setups","Handle deletion races by falling back to create","Avoid holding stale IDs across workspace switches"],"tags":["ai-editor","not-found","stale-state"],"backgroundTag":"record-not-found","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}