{"record":{"id":"f55072265741546b","repo":"siyuan-note/siyuan","slug":"read-only-mode","errorCode":null,"errorMessage":"read-only mode","messagePattern":"read-only mode","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/boot_appearance.go","lineNumber":214,"sourceCode":"// GetBootAppearance 返回当前可用的启动页外观，任何异常和安全模式下均回退默认。\nfunc GetBootAppearance() *BootAppearance {\n\tif util.SafeMode {\n\t\treturn &BootAppearance{Enabled: false}\n\t}\n\tselection, appearance, err := loadSelectedBootAppearance()\n\tif err != nil {\n\t\treturn &BootAppearance{Enabled: false}\n\t}\n\tif selection.Provider == \"\" || selection.Appearance == \"\" {\n\t\treturn &BootAppearance{Enabled: false}\n\t}\n\treturn appearance\n}\n\n// SetBootAppearance 校验并原子持久化启动页外观选择，两项均为空表示恢复默认。\nfunc SetBootAppearance(provider, appearanceID string) (ret BootAppearanceSelection, err error) {\n\tif util.ReadOnly {\n\t\treturn defaultBootAppearanceSelection(), errors.New(\"read-only mode\")\n\t}\n\tprovider = strings.TrimSpace(provider)\n\tappearanceID = strings.TrimSpace(appearanceID)\n\tret = defaultBootAppearanceSelection()\n\tif (provider == \"\") != (appearanceID == \"\") {\n\t\terr = errors.New(\"provider and appearance must both be empty or non-empty\")\n\t\treturn\n\t}\n\tif provider != \"\" {\n\t\tif !bazaar.IsValidPackageName(provider) || !isValidBootAppearanceID(appearanceID) {\n\t\t\terr = ErrBootAppearanceNotFound\n\t\t\treturn\n\t\t}\n\t\tif _, resolveErr := getBootAppearanceByID(provider, appearanceID); resolveErr != nil {\n\t\t\terr = ErrBootAppearanceNotFound\n\t\t\treturn\n\t\t}\n\t\tret.Provider, ret.Appearance = provider, appearanceID","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/boot_appearance.go#L196-L232","documentation":"SetBootAppearance modifies the persisted boot (splash) appearance selection, but the kernel is running in read-only mode (util.ReadOnly), so configuration writes are disabled and the call returns the default selection with this error. Read-only mode is a runtime state (e.g. readonly launch flag / protected environments) in which no data or config mutation is permitted.","triggerScenarios":"Calling SetBootAppearance (via setBootAppearance API or tests) while the kernel runs with read-only mode enabled; attempting to persist any boot appearance selection in a locked/readonly workspace.","commonSituations":"Kernel started with the readonly flag for kiosk/public deployments; automated tooling trying to customize the splash screen on a read-only instance.","solutions":["Restart the kernel without read-only mode, then call SetBootAppearance.","Skip the customization step when util.ReadOnly is true; treat the default selection as final.","If read-only is unintentional, check the launch configuration/flags that set util.ReadOnly.","Use the returned defaultBootAppearanceSelection() as a safe fallback in callers."],"exampleFix":"// before\n_, err := model.SetBootAppearance(provider, id)\n// after\nif util.ReadOnly {\n    return model.DefaultBootAppearanceSelection() // skip write in read-only mode\n}\n_, err := model.SetBootAppearance(provider, id)","handlingStrategy":"fallback","validationCode":"if (kernelReadOnly) skipAppearanceCustomization()","typeGuard":null,"tryCatchPattern":"sel, err := model.SetBootAppearance(provider, id)\nif err != nil && strings.Contains(err.Error(), \"read-only mode\") {\n    sel = model.DefaultBootAppearanceSelection()\n}","preventionTips":["Check util.ReadOnly before attempting any config writes","Design tooling to treat read-only instances as non-configurable","Surface read-only state in UI before offering customization"],"tags":["read-only","boot-appearance","config"],"backgroundTag":"permission-denied","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"}