{"record":{"id":"cfe86f1df53b8d54","repo":"siyuan-note/siyuan","slug":"document-s-cannot-be-pinned","errorCode":null,"errorMessage":"document [%s] cannot be pinned","messagePattern":"document \\[(.+?)\\] cannot be pinned","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/pinned_docs.go","lineNumber":189,"sourceCode":"\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)\n\t\t}\n\t\tial := box.docIAL(bt.Path)\n\t\tif ial == nil {\n\t\t\treturn fmt.Errorf(\"cannot read pinned document [%s]\", id)\n\t\t}\n\t\tif id != bt.BoxID && ial[DocHiddenAttr] == \"true\" {\n\t\t\treturn fmt.Errorf(\"document [%s] cannot be pinned\", id)\n\t\t}\n\t\trefs = append(refs, pinnedDocRef{ID: id, Notebook: bt.BoxID})\n\t}\n\tif action == \"pin\" && selected[targetID] {\n\t\treturn nil\n\t}\n\tremaining := []pinnedDocRef{}","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/pinned_docs.go#L171-L207","documentation":"isPinnableDocument only accepts documents that are notebook roots or regular top-level documents: the block-tree record must exist, tree.ID == tree.RootID, tree.Type == \"d\", and the notebook must not be encrypted. If the ID resolves to a non-document block, a child document without an allowed tree shape, or an unknown/unindexed block (bt == nil), the pin is rejected with this error.","triggerScenarios":"Pinning an ID that resolves to a block tree entry where isPinnableDocument returns false: a non-root block ID (heading, paragraph), a bt == nil (not in block tree), or a doc that is hidden while also being the notebook root ID form (see the DocHiddenAttr check which reuses this message). Also triggered when the id equals bt.BoxID but ial[DocHiddenAttr]==\"true\" at line 200.","commonSituations":"Plugin passes a paragraph or heading block ID instead of a document ID; block tree index is not yet built after import/sync so GetBlockTree returns nil; user tries to pin a hidden document; stale ID after the document was deleted and re-created.","solutions":["Pass the document root ID, not a child block ID.","Wait for indexing to finish (or trigger a reindex) so GetBlockTree can resolve the ID.","Remove the custom-doc-hidden attribute or unhide the document before pinning.","Verify the document still exists and was not replaced by a new ID."],"exampleFix":"// before\nmodel.UpdatePinnedDocs([]string{blockID}, \"pin\", \"\", false)\n// after\nbt := treenode.GetBlockTree(blockID)\nif bt == nil || bt.ID != bt.RootID || bt.Type != \"d\" {\n    return fmt.Errorf(\"%s is not a pinnable document\", blockID)\n}\nmodel.UpdatePinnedDocs([]string{blockID}, \"pin\", \"\", false)","handlingStrategy":"validation","validationCode":"func pinnable(id string) bool {\n    bt := treenode.GetBlockTree(id)\n    return bt != nil && bt.ID == bt.RootID && bt.Type == \"d\" && !model.IsEncryptedBox(bt.BoxID)\n}","typeGuard":"func isDocRoot(bt *treenode.BlockTree) bool { return bt != nil && bt.ID == bt.RootID && bt.Type == \"d\" }","tryCatchPattern":"if err := model.UpdatePinnedDocs(ids, \"pin\", \"\", false); err != nil {\n    if strings.Contains(err.Error(), \"cannot be pinned\") {\n        // re-resolve IDs, drop non-document roots, retry\n    }\n}","preventionTips":["Pass document root IDs, never child block IDs","Confirm GetBlockTree resolves the ID after import/sync before pinning","Unhide documents (custom-doc-hidden != \"true\") before pinning","Check index build completion before pinning freshly imported docs"],"tags":["validation","pinned-docs","document"],"backgroundTag":"invalid-argument-value","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"}