{"record":{"id":"3ffb2db4207fdaae","repo":"siyuan-note/siyuan","slug":"document-version-is-empty","errorCode":null,"errorMessage":"document version is empty","messagePattern":"document version is empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history_diff.go","lineNumber":270,"sourceCode":"\t}\n\tswitch ref.Type {\n\tcase docVersionCurrent:\n\t\tif !ast.IsNodeIDPattern(ref.ID) {\n\t\t\treturn nil, errors.New(\"current document ID is invalid\")\n\t\t}\n\t\tret, err = loadCurrentDocVersion(ref.ID)\n\tcase docVersionHistory:\n\t\tret, err = loadHistoryDocVersion(ref.Path)\n\tcase docVersionSnapshot:\n\t\tret, err = loadSnapshotDocVersion(ref.ID)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported document version type [%s]\", ref.Type)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif nil == ret || (nil == ret.tree && 0 == len(ret.raw)) || (nil != ret.tree && nil == ret.tree.Root) {\n\t\treturn nil, errors.New(\"document version is empty\")\n\t}\n\tif nil != ret.tree && \"\" == ret.rootID {\n\t\tret.rootID = ret.tree.Root.ID\n\t}\n\tif nil != ret.tree && \"\" == ret.title {\n\t\tret.title = ret.tree.Root.IALAttr(\"title\")\n\t}\n\tif \"\" == ret.title {\n\t\tret.title = ret.rootID\n\t}\n\treturn\n}\n\nfunc loadCurrentDocVersion(id string) (ret *loadedDocVersion, err error) {\n\tblockTree := treenode.GetBlockTree(id)\n\tif nil == blockTree {\n\t\treturn nil, ErrTreeNotFound\n\t}","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/history_diff.go#L252-L288","documentation":"Returned by loadDocVersion (history_diff.go:270) when the loader produced no usable content: ret is nil, or both ret.tree and ret.raw are empty, or ret.tree has a nil Root. It fires after the type-specific loader succeeds, meaning the file/snapshot was found but is structurally empty or unparseable in a way the loader represented as an empty struct. Distinct from a parse error (which is stored on loadedDocVersion.parseErr and still returns a non-nil ret).","triggerScenarios":"A history .sy file that is zero bytes; a snapshot whose repo blob decrypted to empty; a tree whose root node was stripped during parse; a code path that returns &loadedDocVersion{} without populating tree or raw.","commonSituations":"Corrupted history file from a crash mid-write; a snapshot taken of an empty doc that was later deleted; manual editing of .sy files; race between doc deletion and history generation.","solutions":["Inspect the underlying file/snapshot on disk to confirm it is non-empty and valid JSON.","If the file is legitimately empty (e.g. an empty doc snapshot), have the loader return a parseErr-style fallback so DiffDocVersions uses the fallback path instead of failing.","Run a workspace integrity check / re-index to regenerate corrupted history entries."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before diffing, confirm the underlying file/snapshot is non-empty.\ninfo, err := os.Stat(absPath)\nif err != nil || info.Size() == 0 {\n    return errors.New(\"document version is empty\")\n}","typeGuard":null,"tryCatchPattern":"diff, err := DiffDocVersions(left, right)\nif err != nil && strings.Contains(err.Error(), \"document version is empty\") {\n    // inspect the source file/snapshot on disk; offer to regenerate history\n}","preventionTips":["Run workspace integrity checks periodically to detect zero-byte .sy files.","If a loader legitimately returns empty content, populate parseErr so DiffDocVersions takes the fallback path.","Log the absPath/repo fileID alongside this error for faster root-cause."],"tags":["history-diff","data-integrity","corruption","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}