{"record":{"id":"5e7a5aea6d21836b","repo":"siyuan-note/siyuan","slug":"history-version-is-not-a-document","errorCode":null,"errorMessage":"history version is not a document","messagePattern":"history version is not a document","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history_diff.go","lineNumber":338,"sourceCode":"\tret, err = filelock.ReadFile(absPath)\n\tif err != nil || !encrypted {\n\t\treturn\n\t}\n\tdek, err := GetDEKIfUnlocked(blockTree.BoxID)\n\tif err != nil {\n\t\treturn nil, errors.New(Conf.Language(314))\n\t}\n\tret, err = DecryptFile(blockTree.BoxID, relPath, dek, ret)\n\treturn\n}\n\nfunc loadHistoryDocVersion(historyPath string) (ret *loadedDocVersion, err error) {\n\tabsPath, err := validateHistoryPath(historyPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !strings.HasSuffix(strings.ToLower(absPath), \".sy\") {\n\t\treturn nil, errors.New(\"history version is not a document\")\n\t}\n\trelPath, err := filepath.Rel(util.HistoryDir, absPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparts := strings.SplitN(filepath.ToSlash(relPath), \"/\", 3)\n\tdata, err := filelock.ReadFile(absPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tciphertext := util.IsCiphertext(data)\n\tif ciphertext {\n\t\tif len(parts) < 3 || !ast.IsNodeIDPattern(parts[1]) || !IsEncryptedBox(parts[1]) {\n\t\t\treturn nil, errors.New(\"encrypted document history is missing valid notebook context\")\n\t\t}\n\t\tHoldBoxReadLock(parts[1])\n\t\tdefer ReleaseBoxReadLock(parts[1])\n\t\tdek, dekErr := GetDEKIfUnlocked(parts[1])","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/history_diff.go#L320-L356","documentation":"Returned by loadHistoryDocVersion (history_diff.go:338) when the validated history path does not end in \".sy\" (case-insensitive). SiYuan documents on disk are .sy files; only those are valid document-version sources. Non-.sy entries under the history tree (assets, conf.json, boxDocMeta) are intentionally rejected here.","triggerScenarios":"POST /api/history/diffDocVersions with a history \"path\" pointing at a directory, an asset file, or a .json metadata file; a frontend that lets the user pick a non-document history row; a hand-crafted historyPath argument.","commonSituations":"User selects an asset or notebook-config row in the history panel; a script that walks the history dir and feeds every file to the diff API.","solutions":["Filter history entries client-side to documents only (the history API already tags entry types).","Before calling loadHistoryDocVersion, verify strings.HasSuffix(strings.ToLower(path), \".sy\").","If the user genuinely wants to diff an asset, use the asset-history endpoint, not the document-diff one."],"exampleFix":"// before\nref := &DocVersionRef{Type: \"history\", Path: pickedPath}\n\n// after\nif !strings.HasSuffix(strings.ToLower(pickedPath), \".sy\") {\n    return errors.New(\"history version is not a document\")\n}\nref := &DocVersionRef{Type: \"history\", Path: pickedPath}","handlingStrategy":"validation","validationCode":"if !strings.HasSuffix(strings.ToLower(historyPath), \".sy\") {\n    return errors.New(\"history version is not a document\")\n}\n// safe to load history doc version","typeGuard":"// isHistoryDoc reports whether a history path is a .sy document.\nfunc isHistoryDoc(p string) bool {\n    return strings.HasSuffix(strings.ToLower(p), \".sy\")\n}","tryCatchPattern":null,"preventionTips":["Filter the history list client-side to document entries only.","Validate the .sy suffix before constructing a history-type DocVersionRef.","Use asset-history endpoints for assets, not the document-diff endpoint."],"tags":["validation","history-diff","file-format"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}