{"record":{"id":"4189937a52e9b032","repo":"siyuan-note/siyuan","slug":"prepare-box-conf-s-failed-w","errorCode":null,"errorMessage":"prepare box conf [%s] failed: %w","messagePattern":"prepare box conf \\[(.+?)\\] failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/box.go","lineNumber":287,"sourceCode":"\t\tlogging.LogErrorf(\"parse box conf [%s] failed: %s\", confPath, err)\n\t\treturn\n\t}\n\n\tif ret.Encrypted {\n\t\tif err = revealBoxMetadataIfUnlocked(box.ID, ret); err != nil {\n\t\t\tlogging.LogErrorf(\"decrypt encrypted notebook metadata [%s] failed: %s\", box.ID, err)\n\t\t}\n\t} else {\n\t\tret.Icon = filterBoxIcon(ret.Icon)\n\t}\n\treturn\n}\n\nfunc (box *Box) SaveConf(conf *conf.BoxConf) error {\n\tconfPath := filepath.Join(util.DataDir, box.ID, \".siyuan/conf.json\")\n\tpersisted, err := prepareBoxConfForSave(box.ID, conf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"prepare box conf [%s] failed: %w\", confPath, err)\n\t}\n\tnewData, err := gulu.JSON.MarshalIndentJSON(persisted, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal box conf [%s] failed: %w\", confPath, err)\n\t}\n\n\toldData, err := filelock.ReadFile(confPath)\n\tif err != nil {\n\t\tif err = box.saveConf0(newData); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn syncBoxConfCryptoBackup(box.ID, persisted)\n\t}\n\n\tif bytes.Equal(newData, oldData) {\n\t\treturn syncBoxConfCryptoBackup(box.ID, persisted)\n\t}\n","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/box.go#L269-L305","documentation":"Box.SaveConf calls prepareBoxConfForSave(box.ID, conf); on error it returns fmt.Errorf('prepare box conf [%s] failed: %w', confPath, err) where confPath is <DataDir>/<boxID>/.siyuan/conf.json. prepareBoxConfForSave (box_conf_crypto.go:88) can fail for several distinct reasons, all wrapped here: nil conf ('notebook configuration is missing'), saving an encrypted notebook as normal ('encrypted notebook cannot be saved as a normal notebook'), an encrypted box in error state ('encrypted notebook is in an error state'), missing encrypted metadata, or a decryption failure on the metadata-reuse path. The %w lets the caller unwrap the real cause.","triggerScenarios":"Any notebook configuration change for an encrypted notebook — sort mode, icon, etc. — coming through the filetree/attr flows that call Box.SaveConf (e.g. SetBlockAttrs/BatchSetBlockAttrs setting an 'icon' on a box-doc at blockial.go:192/238), or a direct notebook settings save, when the encryption state is inconsistent (BoxCrypt nil'd while Encrypted=true, box stuck in EncryptedBoxStateError, DEK not cached so metadata can't be reused/encrypted).","commonSituations":"Unlock failed and left the box in error state; a prior migration cleared BoxCrypt; the conf.json was hand-edited to remove key material; switching encryption off without going through the proper disable flow.","solutions":["Unwrap the returned error to read the underlying message — the fix depends on which prepareBoxConfForSave branch failed.","If the box is in EncryptedBoxStateError, re-unlock it (re-enter the passphrase) to clear the state before saving.","If BoxCrypt is nil while Encrypted=true, do not hand-edit conf.json — re-establish encryption through the UI/CLI.","Ensure a cached DEK exists (notebook unlocked) before changing encrypted-notebook settings."],"exampleFix":"// before\nif err := box.SaveConf(conf); err != nil {\n    logging.LogErrorf(\"save conf failed: %s\", err)\n}\n\n// after: surface the wrapped root cause\nif err := box.SaveConf(conf); err != nil {\n    var root error = err\n    if u := errors.Unwrap(err); u != nil { root = u }\n    logging.LogErrorf(\"save conf failed (root=%s): %s\", root, err)\n}","handlingStrategy":"try-catch","validationCode":"// For encrypted notebooks, ensure a cached DEK and non-error state before saving.\nif model.IsEncryptedBox(boxID) {\n    if model.GetEncryptedBoxState(boxID) == model.EncryptedBoxStateError { unlockFirst(boxID) }\n    if _, ok := model.CachedDEKCopy(boxID); !ok { unlockFirst(boxID) }\n}","typeGuard":null,"tryCatchPattern":"// Always unwrap so the real prepareBoxConfForSave cause is visible.\nif err := box.SaveConf(conf); err != nil {\n    log.Errorw(\"save box conf\", \"box\", box.ID, \"err\", err, \"root\", errors.Unwrap(err))\n    return err\n}","preventionTips":["Keep encrypted notebooks unlocked while their settings are being changed.","Never hand-edit an encrypted notebook's conf.json.","Clear EncryptedBoxStateError (re-unlock) before any SaveConf on the box."],"tags":["notebook","config","encryption","filesystem"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}