{"record":{"id":"edb67d03a078a7f7","repo":"siyuan-note/siyuan","slug":"snapshot-version-is-not-a-document","errorCode":null,"errorMessage":"snapshot version is not a document","messagePattern":"snapshot version is not a document","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history_diff.go","lineNumber":413,"sourceCode":"}\n\nfunc loadSnapshotDocVersion(fileID string) (ret *loadedDocVersion, err error) {\n\tif \"\" == fileID {\n\t\treturn nil, errors.New(\"snapshot file ID is required\")\n\t}\n\tif 1 > len(Conf.Repo.Key) {\n\t\treturn nil, errors.New(Conf.Language(26))\n\t}\n\trepo, err := newRepository()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfile, err := repo.GetFile(fileID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !strings.HasSuffix(strings.ToLower(file.Path), \".sy\") {\n\t\treturn nil, errors.New(\"snapshot version is not a document\")\n\t}\n\trepoPath := strings.TrimPrefix(file.Path, \"/\")\n\tpathParts := strings.SplitN(repoPath, \"/\", 2)\n\tdata, err := repo.OpenFile(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tciphertext := util.IsCiphertext(data)\n\tif ciphertext {\n\t\tif len(pathParts) < 2 || !ast.IsNodeIDPattern(pathParts[0]) || !IsEncryptedBox(pathParts[0]) {\n\t\t\treturn nil, errors.New(\"encrypted snapshot document is missing valid notebook context\")\n\t\t}\n\t\tHoldBoxReadLock(pathParts[0])\n\t\tdefer ReleaseBoxReadLock(pathParts[0])\n\t\tdek, unlockErr := GetDEKIfUnlocked(pathParts[0])\n\t\tif unlockErr != nil {\n\t\t\treturn nil, errors.New(Conf.Language(314))\n\t\t}","sourceCodeStart":395,"sourceCodeEnd":431,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/history_diff.go#L395-L431","documentation":"Returned by loadSnapshotDocVersion (history_diff.go:413) when the repo object resolved by fileID has a path that does not end in \".sy\". The data repo stores many object types (assets, conf, metadata); only .sy entries are documents and only those can be diffed. The check mirrors loadHistoryDocVersion's .sy guard for the snapshot path.","triggerScenarios":"DiffDocVersions with a snapshot fileID that points to a non-document repo object (an asset, a .json config blob); a UI that lists all repo objects rather than only document snapshots; a hand-crafted fileID.","commonSituations":"Frontend bug that exposes raw repo object hashes instead of the curated snapshot list; a plugin enumerating repo objects; corrupted snapshot index that references the wrong hash.","solutions":["Only offer snapshot entries that the kernel's snapshot-listing API marks as documents.","Before calling loadSnapshotDocVersion, confirm the object path ends in .sy (the listing API already provides it).","If a non-document object must be inspected, use the dedicated repo/asset APIs, not the diff endpoint."],"exampleFix":"// before\nref := &DocVersionRef{Type: \"snapshot\", ID: anyRepoHash}\n\n// after\nif !strings.HasSuffix(strings.ToLower(file.Path), \".sy\") {\n    return errors.New(\"snapshot version is not a document\")\n}\nref := &DocVersionRef{Type: \"snapshot\", ID: docHash}","handlingStrategy":"validation","validationCode":"if !strings.HasSuffix(strings.ToLower(file.Path), \".sy\") {\n    return errors.New(\"snapshot version is not a document\")\n}\n// safe to load snapshot doc version","typeGuard":"// isSnapshotDoc reports whether a repo file path is a .sy document.\nfunc isSnapshotDoc(p string) bool {\n    return strings.HasSuffix(strings.ToLower(p), \".sy\")\n}","tryCatchPattern":null,"preventionTips":["Only list repo objects the kernel marks as documents in the snapshot picker.","Validate the .sy suffix before constructing a snapshot-type DocVersionRef.","Use dedicated repo/asset APIs for non-document objects."],"tags":["validation","snapshot","file-format","data-repo"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}