{"record":{"id":"077e4301fcbc6092","repo":"siyuan-note/siyuan","slug":"optionremove-must-name-an-existing-option","errorCode":null,"errorMessage":"optionRemove must name an existing option","messagePattern":"optionRemove must name an existing option","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_key_config.go","lineNumber":221,"sourceCode":"\t\tfor _, option := range options {\n\t\t\tif nil == option || \"\" == strings.TrimSpace(option.Name) {\n\t\t\t\treturn errors.New(\"each option requires a nonempty name\")\n\t\t\t}\n\t\t\tif names[option.Name] {\n\t\t\t\treturn errors.New(\"duplicate option name\")\n\t\t\t}\n\t\t\tif attrView.FilterColorValue(option.Color) != option.Color {\n\t\t\t\treturn errors.New(\"invalid option color\")\n\t\t\t}\n\t\t\tnames[option.Name] = true\n\t\t}\n\t\top.Data = options\n\t\treturn updateAttributeViewColumnOptions(op)\n\t}\n\tif \"optionRemove\" == setting {\n\t\tname, ok := value.(string)\n\t\tif !ok || nil == key.GetOption(name) {\n\t\t\treturn errors.New(\"optionRemove must name an existing option\")\n\t\t}\n\t\top.Data = name\n\t\treturn removeAttributeViewColumnOption(op)\n\t}\n\tvar config struct {\n\t\tName    string  `json:\"name\"`\n\t\tNewName *string `json:\"newName\"`\n\t\tColor   *string `json:\"color\"`\n\t\tDesc    *string `json:\"desc\"`\n\t}\n\tif err := decodeAttributeViewKeyConfig(value, &config); nil != err {\n\t\treturn err\n\t}\n\toption := key.GetOption(config.Name)\n\tif nil == option {\n\t\treturn errors.New(\"optionUpdate must name an existing option\")\n\t}\n\tname, color, desc := option.Name, option.Color, option.Desc","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_key_config.go#L203-L239","documentation":"The 'optionRemove' setting removes a single existing option by exact name. The submitted value must be a string naming an option currently present on the key; otherwise SiYuan refuses, since removing a non-existent option would be a silent no-op masking a client bug.","triggerScenarios":"Calling UpdateAttributeViewKeyConfig with setting \"optionRemove\" where value is not a string (e.g. an object) or the named option does not exist on the field (typo, already removed, different case).","commonSituations":"Plugin caching an out-of-date option list and removing an option deleted moments ago; passing the option object instead of its name; case mismatch between submitted and stored name.","solutions":["Read the current options (e.g. via getAttributeView) and pass the exact existing option name as a plain string","Fix the payload so value is the name string, not an object","Handle already-removed options gracefully instead of re-sending stale names"],"exampleFix":"// before\n{avID, keyID, setting: \"optionRemove\", data: {name: \"Todo\"}}\n// after\n{avID, keyID, setting: \"optionRemove\", data: \"Todo\"}","handlingStrategy":"validation","validationCode":"const exists = key.options?.some(o => o.name === optionName);\nif (typeof optionName !== \"string\" || !exists) throw new Error(`option not present: ${optionName}`);","typeGuard":"const isRemovableOption = (key, name) => typeof name === \"string\" && (key.options ?? []).some(o => o.name === name);","tryCatchPattern":null,"preventionTips":["Fetch the field's current options immediately before removing","Always pass the name string, never the option object"],"tags":["attribute-view","select-options","api"],"backgroundTag":"record-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"}