{"record":{"id":"5ff0eba53fe6922b","repo":"siyuan-note/siyuan","slug":"invalid-document-id-s","errorCode":null,"errorMessage":"invalid document ID [%s]","messagePattern":"invalid document ID \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/pinned_docs.go","lineNumber":175,"sourceCode":"// 根层顺序独立于源文档顺序，按相对位置更新以保留其他窗口新增的入口。\nfunc UpdatePinnedDocs(ids []string, action, targetID string, after bool) error {\n\tif len(ids) == 0 {\n\t\treturn fmt.Errorf(\"document IDs are required\")\n\t}\n\tif action != \"pin\" && action != \"unpin\" {\n\t\treturn fmt.Errorf(\"invalid pinned document action\")\n\t}\n\tpinnedDocsLock.Lock()\n\tdefer pinnedDocsLock.Unlock()\n\tstored, err := readPinnedDocs()\n\tif err != nil {\n\t\treturn err\n\t}\n\tselected := map[string]bool{}\n\trefs := []pinnedDocRef{}\n\tfor _, id := range ids {\n\t\tif !ast.IsNodeIDPattern(id) {\n\t\t\treturn fmt.Errorf(\"invalid document ID [%s]\", id)\n\t\t}\n\t\tif selected[id] {\n\t\t\tcontinue\n\t\t}\n\t\tselected[id] = true\n\t\tif action == \"unpin\" {\n\t\t\tcontinue\n\t\t}\n\t\tbt := treenode.GetBlockTree(id)\n\t\tif bt != nil && IsEncryptedBox(bt.BoxID) {\n\t\t\treturn fmt.Errorf(\"%s\", Conf.Language(396))\n\t\t}\n\t\tif !isPinnableDocument(bt) {\n\t\t\treturn fmt.Errorf(\"document [%s] cannot be pinned\", id)\n\t\t}\n\t\tbox := Conf.Box(bt.BoxID)\n\t\tif box == nil || box.Stat(bt.Path) == nil {\n\t\t\treturn fmt.Errorf(\"document [%s] is unavailable\", id)","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/pinned_docs.go#L157-L193","documentation":"UpdatePinnedDocs validates every document ID passed by the client before changing the pinned-documents list. ast.IsNodeIDPattern requires a canonical 22-character SiYuan node ID (base32-style pattern used for .sy file names). If any id in the ids slice is empty, truncated, or otherwise not a well-formed node ID, the whole update is rejected with this error before any storage mutation.","triggerScenarios":"Calling UpdatePinnedDocs (or the /api/filetree/updatePinnedDocs-style HTTP endpoint) with an ids array containing a malformed string: an empty id, a title pasted instead of an ID, a hand-constructed or truncated ID, or an ID copied from another system.","commonSituations":"Plugins or scripts building the request from stale data; clients that pass document titles or paths instead of block IDs; IDs corrupted by manual editing of the storage file or by an older client version that used a different ID format.","solutions":["Fix the caller to pass real node IDs: fetch them from GetPinnedDocs / the doc tree rather than constructing strings.","Validate each id against ast.IsNodeIDPattern client-side before sending the request.","Check for empty strings or whitespace in the ids array and remove them.","If IDs came from a manually edited pinned-docs storage file, restore IDs from the document tree or let maintainPinnedDocs clean them up."],"exampleFix":"// before\nUpdatePinnedDocs([]string{doc.Title}, \"pin\", \"\", false)\n// after\nUpdatePinnedDocs([]string{doc.ID}, \"pin\", \"\", false)","handlingStrategy":"validation","validationCode":"func canPin(ids []string) bool {\n    for _, id := range ids {\n        if !ast.IsNodeIDPattern(id) { return false }\n    }\n    return len(ids) > 0\n}","typeGuard":"func isValidDocID(id string) bool { return ast.IsNodeIDPattern(id) }","tryCatchPattern":"if err := model.UpdatePinnedDocs(ids, \"pin\", \"\", false); err != nil {\n    if strings.HasPrefix(err.Error(), \"invalid document ID\") {\n        // refresh IDs from GetPinnedDocs and retry once\n    }\n}","preventionTips":["Always take document IDs from API responses (GetPinnedDocs, doc tree), never construct them by hand","Validate with ast.IsNodeIDPattern before each call","Strip whitespace/empty entries from the ids array","Keep client and kernel versions in sync to avoid ID format drift"],"tags":["validation","document-id","pinned-docs"],"backgroundTag":"invalid-identifier-format","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}