siyuan-note/siyuan · error

optionRemove must name an existing option

Error message

optionRemove must name an existing option

What it means

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.

Solutions

  1. Read the current options (e.g. via getAttributeView) and pass the exact existing option name as a plain string
  2. Fix the payload so value is the name string, not an object
  3. Handle already-removed options gracefully instead of re-sending stale names

Example fix

// before
{avID, keyID, setting: "optionRemove", data: {name: "Todo"}}
// after
{avID, keyID, setting: "optionRemove", data: "Todo"}
Defensive patterns

Strategy: validation

Validate before calling

const exists = key.options?.some(o => o.name === optionName);
if (typeof optionName !== "string" || !exists) throw new Error(`option not present: ${optionName}`);

Type guard

const isRemovableOption = (key, name) => typeof name === "string" && (key.options ?? []).some(o => o.name === name);

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/077e4301fcbc6092. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_key_config.go:221

		for _, option := range options {
			if nil == option || "" == strings.TrimSpace(option.Name) {
				return errors.New("each option requires a nonempty name")
			}
			if names[option.Name] {
				return errors.New("duplicate option name")
			}
			if attrView.FilterColorValue(option.Color) != option.Color {
				return errors.New("invalid option color")
			}
			names[option.Name] = true
		}
		op.Data = options
		return updateAttributeViewColumnOptions(op)
	}
	if "optionRemove" == setting {
		name, ok := value.(string)
		if !ok || nil == key.GetOption(name) {
			return errors.New("optionRemove must name an existing option")
		}
		op.Data = name
		return removeAttributeViewColumnOption(op)
	}
	var config struct {
		Name    string  `json:"name"`
		NewName *string `json:"newName"`
		Color   *string `json:"color"`
		Desc    *string `json:"desc"`
	}
	if err := decodeAttributeViewKeyConfig(value, &config); nil != err {
		return err
	}
	option := key.GetOption(config.Name)
	if nil == option {
		return errors.New("optionUpdate must name an existing option")
	}
	name, color, desc := option.Name, option.Color, option.Desc

View on GitHub (pinned to 9f775e8a12)