{"record":{"id":"6adc41efed7036e9","repo":"siyuan-note/siyuan","slug":"invalid-appearance-id","errorCode":null,"errorMessage":"invalid appearance ID","messagePattern":"invalid appearance ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/boot_appearance.go","lineNumber":377,"sourceCode":"\tif err != nil {\n\t\treturn\n\t}\n\tcurrent := defaultBootAppearanceSelection()\n\tif err = gulu.JSON.UnmarshalJSON(data, &current); err != nil || current != expected {\n\t\treturn\n\t}\n\tdata, err = gulu.JSON.MarshalIndentJSON(defaultBootAppearanceSelection(), \"\", \"\\t\")\n\tif err != nil {\n\t\treturn\n\t}\n\tif err = filelock.WriteFile(configPath, data); err != nil {\n\t\tlogging.LogWarnf(\"clear invalid boot appearance selection failed: %s\", err)\n\t}\n}\n\nfunc loadBootAppearance(pluginDir string, pkg *bazaar.Package, appearanceID string) (ret *BootAppearance, err error) {\n\tif !isValidBootAppearanceID(appearanceID) {\n\t\terr = errors.New(\"invalid appearance ID\")\n\t\treturn\n\t}\n\tappearanceDir := filepath.Join(pluginDir, bootAppearanceDirName, appearanceID)\n\tif err = validateBootAppearancePackage(pluginDir, appearanceDir); err != nil {\n\t\treturn\n\t}\n\tmanifestPath, _, resolveErr := validateBootAppearanceResource(pluginDir, appearanceDir, bootAppearanceManifestName, \"manifest\")\n\tif resolveErr != nil {\n\t\terr = resolveErr\n\t\treturn\n\t}\n\tdata, readErr := filelock.ReadFile(manifestPath)\n\tif readErr != nil {\n\t\terr = readErr\n\t\treturn\n\t}\n\tmanifest := &bootAppearanceManifest{}\n\tif err = gulu.JSON.UnmarshalJSON(data, manifest); err != nil {","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/boot_appearance.go#L359-L395","documentation":"loadBootAppearance rejects the appearance ID before touching disk because it fails isValidBootAppearanceID: it must match the boot appearance ID pattern and be at most 64 characters. The kernel only loads boot appearances whose directory/manifest ID conform to this safe-identifier format, which prevents path traversal and filesystem-incompatible names.","triggerScenarios":"GetBootAppearances or getBootAppearanceByID calling loadBootAppearance with an appearanceID containing characters outside the allowed pattern (spaces, slashes, backslashes, non-ASCII), an empty string, or an ID longer than 64 characters. In practice this comes from a persisted selection file or a manifest declaring a boot appearance whose ID violates the pattern.","commonSituations":"A plugin author names the appearance folder 'My Boot Screen! v1' instead of a slug like 'my-boot-screen'; a copied manifest keeps an old ID with trailing whitespace; a corrupted boot-appearance.json selection file yields a malformed ID (this is usually caught earlier and mapped to ErrBootAppearanceNotFound, but loadBootAppearance re-checks defensively).","solutions":["Rename the appearance directory under data/plugins/<provider>/appearances/ (or the plugin's boot appearance folder) so the ID matches the allowed pattern and is <= 64 characters, e.g. 'my-boot-screen'","Update the appearance's manifest.json so its ID field exactly matches the renamed directory name","If a bad selection was persisted, delete or reset the boot appearance selection config so the kernel falls back to the default boot appearance","Update the plugin's plugin.json bootAppearances list to reference the corrected ID"],"exampleFix":"// before (manifest.json)\n{\"id\": \"My Boot Screen v1.0!\", ...}\n// after\n{\"id\": \"my-boot-screen-v1\", ...}\n// directory renamed to match: appearances/my-boot-screen-v1/","handlingStrategy":"validation","validationCode":"function isValidAppearanceID(id) {\n  return typeof id === \"string\" && id.length > 0 && id.length <= 64 && /^[a-z0-9][a-z0-9-]*$/.test(id);\n}\n// only request boot appearances whose ID passes isValidAppearanceID","typeGuard":"const isAppearanceID = (v: unknown): v is string =>\n  typeof v === \"string\" && v.length <= 64 && /^[a-z0-9][a-z0-9-]*$/.test(v);","tryCatchPattern":null,"preventionTips":["Always generate appearance IDs as lowercase slugs (letters, digits, hyphens)","Keep IDs at or under 64 characters","Derive the manifest id from the directory name programmatically so they can never diverge","Never include spaces, punctuation, or non-ASCII in appearance folder names"],"tags":["validation","plugin","identifier","config"],"backgroundTag":"invalid-identifier-format","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}