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
- Always send both fields together: both empty to reset, both filled to select
- Coalesce in the caller: if either field is empty, treat the request as a reset (pass both as "")
- 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
- Send both fields as one unit from the UI/API layer
- Default missing fields to "" together, never independently
- Treat selection as a single (provider, appearance) tuple
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
- invalid appearance ID
- block [ ] is not a document that can declare a child…
- boot appearance asset forbidden
- boot appearance not found
- Conf.Language(112)
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)