{"record":{"id":"4db7218a0fb0ffbc","repo":"siyuan-note/siyuan","slug":"unmarshal-box-document-metadata-failed-w","errorCode":null,"errorMessage":"unmarshal box document metadata failed: %w","messagePattern":"unmarshal box document metadata failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/box_doc.go","lineNumber":71,"sourceCode":"func boxDocPath(boxID string) string {\n\tif \"\" == boxID {\n\t\treturn \"\"\n\t}\n\treturn \"/\" + boxID + \".sy\"\n}\n\nfunc readBoxDocID(boxID string) (ret string, err error) {\n\tdata, err := filelock.ReadFile(boxDocMetaPath(boxID))\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\terr = nil\n\t\t}\n\t\treturn\n\t}\n\n\tmeta := &boxDocMeta{}\n\tif err = gulu.JSON.UnmarshalJSON(data, meta); err != nil {\n\t\treturn \"\", fmt.Errorf(\"unmarshal box document metadata failed: %w\", err)\n\t}\n\tif boxDocMetaSpec != meta.Spec {\n\t\treturn \"\", fmt.Errorf(\"unsupported box document metadata spec [%d]\", meta.Spec)\n\t}\n\tif !ast.IsNodeIDPattern(meta.BoxDocID) {\n\t\treturn \"\", fmt.Errorf(\"invalid box document ID [%s]\", meta.BoxDocID)\n\t}\n\tif boxID != meta.BoxDocID {\n\t\treturn \"\", fmt.Errorf(\"box document ID [%s] does not match box ID [%s]\", meta.BoxDocID, boxID)\n\t}\n\treturn boxID, nil\n}\n\nfunc writeBoxDocID(boxID string) error {\n\tmeta := &boxDocMeta{Spec: boxDocMetaSpec, BoxDocID: boxID}\n\tdata, err := gulu.JSON.MarshalIndentJSON(meta, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal box document metadata failed: %w\", err)","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/box_doc.go#L53-L89","documentation":"readBoxDocID parses <workspace>/data/<notebookID>/.siyuan/boxDoc.json, which binds the notebook to its box document (spec + boxDocID, kernel/model/box_doc.go:39-47). If the file exists but gulu.JSON cannot unmarshal it, the parse error is wrapped with this message. A missing file is fine (returns nil error); only malformed content fails.","triggerScenarios":"Any notebook open/mount path that calls readBoxDocID when boxDoc.json is truncated or corrupt: partial write after a crash, external editor saving invalid JSON, disk or sync corruption.","commonSituations":"Power loss mid-write; sync clients (Dropbox/OneDrive) producing conflict-truncated files; hand-editing the .siyuan metadata directory.","solutions":["Restore data/<boxID>/.siyuan/boxDoc.json from backup/history","If no backup exists, delete the corrupted boxDoc.json — readBoxDocID tolerates absence and the kernel rewrites it (spec 1, boxDocID = box ID) on next notebook write","Exclude the workspace from sync-agent partial writes, or pause sync during kernel shutdown"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { readBoxDocID(boxID) } catch (e) { if (e.Contains(\"unmarshal box document metadata\")) { restoreOrDeleteBoxDocJson(boxID); /* kernel regenerates it */ } }","preventionTips":["Let the kernel shut down cleanly so boxDoc.json is never truncated","Pause sync agents during kernel shutdown","Restore .siyuan metadata from backup rather than hand-fixing JSON"],"tags":["notebook","json","corruption","filesystem"],"backgroundTag":"json-parse-error","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}