{"record":{"id":"c8516b070ebbce4b","repo":"siyuan-note/siyuan","slug":"ai-editor-action-must-not-be-empty","errorCode":null,"errorMessage":"AI editor action must not be empty","messagePattern":"AI editor action must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/ai_editor.go","lineNumber":72,"sourceCode":"\nfunc GetAIEditorActions() (ret []*AIEditorAction, err error) {\n\twaitForSyncingStorages()\n\taiEditorActionsLock.Lock()\n\tdefer aiEditorActionsLock.Unlock()\n\n\tdata, err := loadAIEditorActions()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err = migrateLegacyAIEditorActions(data, !util.ReadOnly); err != nil {\n\t\treturn nil, err\n\t}\n\treturn data.Actions, nil\n}\n\nfunc SaveAIEditorAction(action *AIEditorAction) (ret *AIEditorAction, err error) {\n\tif action == nil || (action.Name == \"\" && action.Action == \"\") {\n\t\treturn nil, errors.New(\"AI editor action must not be empty\")\n\t}\n\tif action.ID != \"\" && !ast.IsNodeIDPattern(action.ID) {\n\t\treturn nil, errors.New(\"invalid AI editor action ID\")\n\t}\n\n\twaitForSyncingStorages()\n\taiEditorActionsLock.Lock()\n\tdefer aiEditorActionsLock.Unlock()\n\n\tdata, err := loadAIEditorActions()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err = migrateLegacyAIEditorActions(data, true); err != nil {\n\t\treturn nil, err\n\t}\n\n\tret = &AIEditorAction{","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/ai_editor.go#L54-L90","documentation":"SaveAIEditorAction validates that the supplied action carries at least a name or an action prompt. A nil pointer, or an object with both Name and Action empty, has nothing to persist and is rejected up-front.","triggerScenarios":"Calling SaveAIEditorAction(nil) or SaveAIEditorAction(&AIEditorAction{}) (both fields empty) via the saveAIEditorAction API or tests.","commonSituations":"Frontend form submitted without filling fields; API call with an empty JSON body deserialized to a zero-value struct; client bug sending null payload.","solutions":["Ensure the request body includes a non-empty name or action prompt","Return a validation error in the client before calling the API when both fields are blank","Check the frontend form is binding inputs to the payload correctly"],"exampleFix":"// before\nawait fetchPost('/api/ai/saveAIEditorAction', {})\n// after\nawait fetchPost('/api/ai/saveAIEditorAction', {name: 'Translate', action: 'Translate the selection to English'})","handlingStrategy":"validation","validationCode":"if (!payload || (!payload.name && !payload.action)) { throw new Error('Name or action prompt is required') }","typeGuard":"const isValidActionInput = (a) => a != null && (typeof a.name === 'string' && a.name.trim() !== '' || typeof a.action === 'string' && a.action.trim() !== '')","tryCatchPattern":"try { await saveAction(a) } catch (e) { if (e.message.includes('must not be empty')) showToast('Fill in name or prompt') }","preventionTips":["Validate the form before submitting","Never post empty bodies to the save endpoint","Require at least the action prompt in UI flows"],"tags":["validation","ai-editor","empty-input"],"backgroundTag":"empty-required-field","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"}