{"record":{"id":"0d8cb4f281843567","repo":"siyuan-note/siyuan","slug":"current-document-id-is-invalid","errorCode":null,"errorMessage":"current document ID is invalid","messagePattern":"current document ID is invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history_diff.go","lineNumber":122,"sourceCode":"\tstart      int\n\tend        int\n\tstoredRuns []string\n\tsignature  string\n}\n\ntype docDiffLCSBudget struct {\n\tremaining int\n}\n\n// ResolveDocVersionBoxID 返回文档版本引用中明确记录的加密笔记本 ID。\nfunc ResolveDocVersionBoxID(ref *DocVersionRef) (string, error) {\n\tif ref == nil {\n\t\treturn \"\", errors.New(\"document version is required\")\n\t}\n\tswitch ref.Type {\n\tcase docVersionCurrent:\n\t\tif !ast.IsNodeIDPattern(ref.ID) {\n\t\t\treturn \"\", errors.New(\"current document ID is invalid\")\n\t\t}\n\t\tblockTree := treenode.GetBlockTree(ref.ID)\n\t\tif blockTree == nil {\n\t\t\treturn \"\", ErrTreeNotFound\n\t\t}\n\t\tif IsEncryptedBox(blockTree.BoxID) {\n\t\t\treturn blockTree.BoxID, nil\n\t\t}\n\t\treturn \"\", nil\n\tcase docVersionHistory:\n\t\tabsPath, err := validateHistoryPath(ref.Path)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tboxID := ExtractBoxIDFromHistoryPath(absPath)\n\t\tif IsEncryptedBox(boxID) {\n\t\t\treturn boxID, nil\n\t\t}","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/history_diff.go#L104-L140","documentation":"Returned by ResolveDocVersionBoxID (history_diff.go:122) when ref.Type == \"current\" but ref.ID fails ast.IsNodeIDPattern. SiYuan block/document IDs are fixed-format 20-character base36-ish tokens; any other shape cannot be looked up in the block tree. The guard prevents a pointless treenode.GetBlockTree lookup and catches malformed input early.","triggerScenarios":"POST /api/history/diffDocVersions with {\"type\":\"current\",\"id\":\"<garbage>\"} or an empty id; a plugin passing a rootID truncated or with whitespace; copy-paste of a 14-char timestamp ID instead of the 20-char block ID.","commonSituations":"UI passes a doc ID from a stale clipboard; a script concatenates IDs incorrectly; version mismatch where older 14-char IDs are fed to newer kernels.","solutions":["Validate the ID with ast.IsNodeIDPattern(ref.ID) at the API boundary and return a 400-style message before calling ResolveDocVersionBoxID.","Confirm the ID is the document root ID (20 chars) and not a heading/paragraph child block ID or a timestamp.","If the ID comes from a URL/clipboard, trim whitespace and verify len==20 and all chars are in the node-ID alphabet."],"exampleFix":"// before\nref := &DocVersionRef{Type: \"current\", ID: userInput}\n\n// after\nif ref.Type == \"current\" && !ast.IsNodeIDPattern(ref.ID) {\n    return fmt.Errorf(\"current document ID is invalid\")\n}\nref := &DocVersionRef{Type: \"current\", ID: userInput}","handlingStrategy":"validation","validationCode":"if ref == nil || ref.Type != docVersionCurrent {\n    return // nothing to validate here\n}\nif !ast.IsNodeIDPattern(ref.ID) {\n    return fmt.Errorf(\"current document ID is invalid\")\n}\n// safe to call ResolveDocVersionBoxID(ref)","typeGuard":"// isValidCurrentDocRef reports whether ref is a current-type ref with a valid 20-char block ID.\nfunc isValidCurrentDocRef(ref *DocVersionRef) bool {\n    return ref != nil && ref.Type == docVersionCurrent && ast.IsNodeIDPattern(ref.ID)\n}","tryCatchPattern":null,"preventionTips":["Source document IDs only from treenode.GetBlockTree or the editor root block, never from typed text.","Validate IDs at the API layer before constructing a DocVersionRef.","Trim and length-check (20 chars) IDs from URLs/clipboards."],"tags":["validation","history-diff","block-id","api"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}