{"record":{"id":"32a31e0d5ba65dda","repo":"siyuan-note/siyuan","slug":"invalid-pinned-document-s","errorCode":null,"errorMessage":"invalid pinned document [%s]","messagePattern":"invalid pinned document \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/pinned_docs.go","lineNumber":62,"sourceCode":"\tret = pinnedDocsStorage{Version: 1, Docs: []pinnedDocRef{}}\n\tdata, err := filelock.ReadFile(filepath.Join(util.DataDir, \"storage\", \"pinned-docs.json\"))\n\tif os.IsNotExist(err) {\n\t\treturn ret, nil\n\t}\n\tif err != nil {\n\t\treturn\n\t}\n\tret = pinnedDocsStorage{}\n\tif err = json.Unmarshal(data, &ret); err != nil {\n\t\treturn\n\t}\n\tif ret.Version != 1 || ret.Docs == nil {\n\t\treturn ret, fmt.Errorf(\"unsupported pinned documents version [%d]\", ret.Version)\n\t}\n\tseen := map[string]bool{}\n\tfor _, doc := range ret.Docs {\n\t\tif !ast.IsNodeIDPattern(doc.ID) || !ast.IsNodeIDPattern(doc.Notebook) || seen[doc.ID] {\n\t\t\treturn ret, fmt.Errorf(\"invalid pinned document [%s]\", doc.ID)\n\t\t}\n\t\tseen[doc.ID] = true\n\t}\n\treturn\n}\n\nfunc writePinnedDocs(data pinnedDocsStorage) error {\n\tencoded, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdir := filepath.Join(util.DataDir, \"storage\")\n\tif err = os.MkdirAll(dir, 0755); err != nil {\n\t\treturn err\n\t}\n\tif err = filelock.WriteFile(filepath.Join(dir, \"pinned-docs.json\"), encoded); err != nil {\n\t\treturn err\n\t}","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/pinned_docs.go#L44-L80","documentation":"readPinnedDocs throws \"invalid pinned document [%s]\" when a stored pinned entry has an ID or notebook that is not a valid SiYuan node-ID pattern, or when the same document ID appears twice in the docs array. It prevents malformed pin references from leaking into the file tree.","triggerScenarios":"Loading pinned docs where any entry's id or notebook fails ast.IsNodeIDPattern (e.g. empty string, random text, wrong format) or a duplicate ID exists in pinned-docs.json.","commonSituations":"Hand-editing pinned-docs.json with wrong IDs; corruption from a bad sync merge; entries left over from a defective older version; copying entries and forgetting to change the ID.","solutions":["Open data/storage/pinned-docs.json and fix/remove entries whose id or notebook is not in the 20-char node-ID form (e.g. 20240101120000-abcdefg)","Remove duplicate entries so each document ID appears at most once","Re-pin the documents from the UI, which rewrites the file with valid IDs","Delete pinned-docs.json to reset if unsure; pins are easily recreated"],"exampleFix":"// before\n{\"version\":1,\"docs\":[{\"id\":\"my-doc\",\"notebook\":\"\"}]}\n// after\n{\"version\":1,\"docs\":[{\"id\":\"20240101120000-abc1234\",\"notebook\":\"20240101120000-box5678\"}]}","handlingStrategy":"validation","validationCode":"// Validate node-ID shape before writing pin refs\nre := regexp.MustCompile(`^\\d{14}-[0-9a-z]{7}$`)\nvalid := re.MatchString(id) && re.MatchString(notebookID)","typeGuard":null,"tryCatchPattern":"docs, err := GetPinnedDocs()\nif err != nil && strings.Contains(err.Error(), \"invalid pinned document\") {\n\t// repair: rewrite the file keeping only entries with valid unique IDs\n}","preventionTips":["Always copy node IDs from SiYuan (copy block ID) rather than typing them","Deduplicate IDs before persisting pin lists","Never leave id or notebook empty in pinned-docs.json","Reset the file if unsure; pins are trivially recreated in the UI"],"tags":["go","json","storage","validation"],"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"}