{"record":{"id":"aff246df23331d05","repo":"siyuan-note/siyuan","slug":"marshal-box-conf-s-failed-w","errorCode":null,"errorMessage":"marshal box conf [%s] failed: %w","messagePattern":"marshal box conf \\[(.+?)\\] failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/box.go","lineNumber":291,"sourceCode":"\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\n\tif err = box.saveConf0(newData); err != nil {\n\t\treturn err\n\t}\n\treturn syncBoxConfCryptoBackup(box.ID, persisted)","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/box.go#L273-L309","documentation":"In Box.SaveConf, after prepareBoxConfForSave succeeds, gulu.JSON.MarshalIndentJSON(persisted, '', '  ') is called; on failure it returns fmt.Errorf('marshal box conf [%s] failed: %w', confPath, err). conf.BoxConf is a plain serializable struct, so under normal operation this never fires — it would only fire if the struct gained a non-marshalable field (chan/func/pointer cycle) or encoding encountered a truly malformed value.","triggerScenarios":"Effectively only reachable from a bug in the BoxConf type definition (a developer adding an unserializable field) or a deeply corrupted in-memory BoxConf. Not a runtime/environmental failure.","commonSituations":"A code change introduced an unexported chan/func field on BoxConf or a circular reference between conf structs; an experimental branch mutated the struct in a non-serializable way.","solutions":["Inspect the BoxConf struct definition (kernel/conf) for any field that JSON cannot encode (func, chan, unsafe.Pointer, or a cycle).","Add json:\"-\" tags or remove the offending field; ensure all fields are plain data.","Write a unit test that round-trips conf.NewBoxConf() through json.Marshal to catch regressions."],"exampleFix":"// before: a func field breaks marshalling\ntype BoxConf struct {\n    Encrypted bool\n    onChange  func()\n}\n\n// after: exclude non-serializable fields\ntype BoxConf struct {\n    Encrypted bool\n    onChange  func() `json:\"-\"`\n}","handlingStrategy":"validation","validationCode":"// Catch marshal regressions in tests before they hit production.\nfunc TestBoxConfSerializable(t *testing.T) {\n    b, err := json.Marshal(conf.NewBoxConf())\n    if err != nil || !json.Valid(b) { t.Fatalf(\"BoxConf not serializable: %v\", err) }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not add func/chan fields to BoxConf without a json:\"-\" tag.","Add a JSON round-trip test for any struct persisted to conf.json."],"tags":["notebook","config","serialization","bug"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}