siyuan-note/siyuan · error

provider and appearance must both be empty or non-empty

Error message

provider and appearance must both be empty or non-empty

What it means

SetBootAppearance requires the provider and appearanceID to be consistent: either both empty (restore default) or both non-empty (select an appearance). A half-specified selection is ambiguous, so it is rejected with 'provider and appearance must both be empty or non-empty'.

Solutions

  1. Always send both fields together: both empty to reset, both filled to select
  2. Coalesce in the caller: if either field is empty, treat the request as a reset (pass both as "")
  3. Fix UI/API binding so the two parameters are not partially populated

Example fix

// before
model.SetBootAppearance("my-plugin", "") // mismatched
// after
if (provider == "") != (appearanceID == "") { provider, appearanceID = "", "" } // reset
model.SetBootAppearance(provider, appearanceID)
Defensive patterns

Strategy: validation

Validate before calling

if (provider == "") != (appearanceID == "") { return errors.New("provider and appearance must both be set or both empty") }

Type guard

func paired(p, a string) (string, string, bool) { if (p == "") != (a == "") { return "", "", false }; return p, a, true }

Try / catch

if _, err := model.SetBootAppearance(p, a); err != nil && strings.Contains(err.Error(), "both be empty or non-empty") {
  // fix the pairing and retry or reset
}

Prevention

When it happens

Trigger: Calling SetBootAppearance with exactly one of the two arguments set — e.g. provider set but appearanceID "" (or vice versa) after trimming.

Common situations: API callers that map two optional query params independently and pass through unset ones as empty strings; UI code updating only one field at a time.

Related errors


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

Appendix: source

Thrown at kernel/model/boot_appearance.go:220

	if err != nil {
		return &BootAppearance{Enabled: false}
	}
	if selection.Provider == "" || selection.Appearance == "" {
		return &BootAppearance{Enabled: false}
	}
	return appearance
}

// SetBootAppearance 校验并原子持久化启动页外观选择,两项均为空表示恢复默认。
func SetBootAppearance(provider, appearanceID string) (ret BootAppearanceSelection, err error) {
	if util.ReadOnly {
		return defaultBootAppearanceSelection(), errors.New("read-only mode")
	}
	provider = strings.TrimSpace(provider)
	appearanceID = strings.TrimSpace(appearanceID)
	ret = defaultBootAppearanceSelection()
	if (provider == "") != (appearanceID == "") {
		err = errors.New("provider and appearance must both be empty or non-empty")
		return
	}
	if provider != "" {
		if !bazaar.IsValidPackageName(provider) || !isValidBootAppearanceID(appearanceID) {
			err = ErrBootAppearanceNotFound
			return
		}
		if _, resolveErr := getBootAppearanceByID(provider, appearanceID); resolveErr != nil {
			err = ErrBootAppearanceNotFound
			return
		}
		ret.Provider, ret.Appearance = provider, appearanceID
	}

	data, err := gulu.JSON.MarshalIndentJSON(ret, "", "\t")
	if err != nil {
		return ret, err
	}

View on GitHub (pinned to 9f775e8a12)