siyuan-note/siyuan · error
invalid appearance ID
Error message
invalid appearance ID
What it means
loadBootAppearance validates the appearance ID before attempting to read the appearance's directory under the plugin. An ID that doesn't match the strict kebab-case pattern ^[a-z0-9]+(-[a-z0-9]+)*$ is rejected immediately with 'invalid appearance ID', preventing path/lookup issues from malformed names.
Solutions
- Rename the appearance directory inside the plugin to lowercase kebab-case (e.g. my-appearance)
- Validate the ID against ^[a-z0-9]+(-[a-z0-9]+)*$ before calling
- Get a valid ID from GetBootAppearances rather than constructing it from the filesystem or user input
Example fix
// before
app, err := model.GetBootAppearanceByID("plugin", "Dark Theme")
// after
app, err := model.GetBootAppearanceByID("plugin", "dark-theme") Defensive patterns
Strategy: validation
Validate before calling
var idRe = regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`)
if !idRe.MatchString(appearanceID) { return errors.New("invalid appearance ID") } Type guard
func isValidAppearanceID(id string) bool { return regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`).MatchString(id) } Try / catch
app, err := loadBootAppearance(dir, pkg, id)
if err != nil && strings.Contains(err.Error(), "invalid appearance ID") {
// skip or normalize the ID and retry
} Prevention
- Name appearance directories in strict lowercase kebab-case
- Derive IDs from GetBootAppearances listings, not raw filenames
- Normalize user input to kebab-case before lookup
When it happens
Trigger: getBootAppearanceByID or GetBootAppearances reaching loadBootAppearance with an appearance ID containing uppercase letters, underscores, spaces, slashes, empty string, or repeated/leading/trailing hyphens.
Common situations: Directory names inside the plugin's appearance folder that aren't kebab-case (e.g. 'MyTheme', 'old_theme'); IDs derived from user input or filenames; plugin packages shipped with non-conforming appearance folder names.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- provider and appearance must both be empty or non-empty
- 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/6adc41efed7036e9.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/boot_appearance.go:377
if err != nil {
return
}
current := defaultBootAppearanceSelection()
if err = gulu.JSON.UnmarshalJSON(data, ¤t); err != nil || current != expected {
return
}
data, err = gulu.JSON.MarshalIndentJSON(defaultBootAppearanceSelection(), "", "\t")
if err != nil {
return
}
if err = filelock.WriteFile(configPath, data); err != nil {
logging.LogWarnf("clear invalid boot appearance selection failed: %s", err)
}
}
func loadBootAppearance(pluginDir string, pkg *bazaar.Package, appearanceID string) (ret *BootAppearance, err error) {
if !isValidBootAppearanceID(appearanceID) {
err = errors.New("invalid appearance ID")
return
}
appearanceDir := filepath.Join(pluginDir, bootAppearanceDirName, appearanceID)
if err = validateBootAppearancePackage(pluginDir, appearanceDir); err != nil {
return
}
manifestPath, _, resolveErr := validateBootAppearanceResource(pluginDir, appearanceDir, bootAppearanceManifestName, "manifest")
if resolveErr != nil {
err = resolveErr
return
}
data, readErr := filelock.ReadFile(manifestPath)
if readErr != nil {
err = readErr
return
}
manifest := &bootAppearanceManifest{}
if err = gulu.JSON.UnmarshalJSON(data, manifest); err != nil {View on GitHub (pinned to 9f775e8a12)