{"record":{"id":"38b3503d815727aa","repo":"siyuan-note/siyuan","slug":"snapshot-file-id-is-required","errorCode":null,"errorMessage":"snapshot file ID is required","messagePattern":"snapshot file ID is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history_diff.go","lineNumber":399,"sourceCode":"\t\t\tlarge:    1024*1024 <= len(data),\n\t\t\tboxID:    boxID,\n\t\t\thistory:  historyRoot,\n\t\t}, nil\n\t}\n\treturn &loadedDocVersion{\n\t\ttree:    tree,\n\t\ttitle:   tree.Root.IALAttr(\"title\"),\n\t\trootID:  tree.Root.ID,\n\t\traw:     data,\n\t\tlarge:   1024*1024 <= len(data),\n\t\tboxID:   boxID,\n\t\thistory: historyRoot,\n\t}, nil\n}\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)","sourceCodeStart":381,"sourceCodeEnd":417,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/history_diff.go#L381-L417","documentation":"Returned by loadSnapshotDocVersion (history_diff.go:399) when the fileID argument is empty. Snapshots live in the data repo (dejavu) and are addressed by their repo object hash; an empty ID cannot resolve to any object. The guard runs before the repo-key check so callers get the most specific message.","triggerScenarios":"POST /api/history/diffDocVersions with {\"type\":\"snapshot\",\"id\":\"\"} or a missing id field; a frontend bug that clears the snapshot ID before submitting; a plugin that constructs the ref conditionally.","commonSituations":"Frontend state race where the snapshot list is still loading when the user clicks diff; a refactoring that renamed the JSON field without updating all callers.","solutions":["Require a non-empty snapshot id at the API boundary via BindJsonArg(\"id\", &id, true, true) when type==\"snapshot\".","On the client, disable the diff button until a snapshot row is selected.","Log the incoming request body when this fires to catch field-name mismatches (id vs snapshot)."],"exampleFix":"// before\nref := &DocVersionRef{Type: \"snapshot\"} // id missing\n\n// after\nif ref.Type == \"snapshot\" && ref.ID == \"\" {\n    return errors.New(\"snapshot file ID is required\")\n}\nref := &DocVersionRef{Type: \"snapshot\", ID: fileHash}","handlingStrategy":"validation","validationCode":"if ref.Type == docVersionSnapshot && ref.ID == \"\" {\n    return errors.New(\"snapshot file ID is required\")\n}\n// safe to load snapshot doc version","typeGuard":"// validSnapshotRef reports whether ref is a snapshot with a non-empty fileID.\nfunc validSnapshotRef(ref *DocVersionRef) bool {\n    return ref != nil && ref.Type == docVersionSnapshot && ref.ID != \"\"\n}","tryCatchPattern":null,"preventionTips":["Require snapshot id via BindJsonArg(\"id\", &id, true, true) when type==\"snapshot\".","Disable the diff button until a snapshot row is selected.","Log the request body when this fires to catch field-name mismatches."],"tags":["validation","snapshot","history-diff","data-repo"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}