{"record":{"id":"bcb6ddc855479dad","repo":"siyuan-note/siyuan","slug":"pinned-target-s-is-unavailable","errorCode":null,"errorMessage":"pinned target [%s] is unavailable","messagePattern":"pinned target \\[(.+?)\\] is unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/pinned_docs.go","lineNumber":223,"sourceCode":"\t\treturn nil\n\t}\n\tremaining := []pinnedDocRef{}\n\tfor _, ref := range stored.Docs {\n\t\tif !selected[ref.ID] {\n\t\t\tremaining = append(remaining, ref)\n\t\t}\n\t}\n\tindex := 0\n\tif targetID != \"\" && action == \"pin\" {\n\t\tindex = -1\n\t\tfor i, ref := range remaining {\n\t\t\tif ref.ID == targetID {\n\t\t\t\tindex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif index < 0 {\n\t\t\treturn fmt.Errorf(\"pinned target [%s] is unavailable\", targetID)\n\t\t}\n\t\tif after {\n\t\t\tindex++\n\t\t}\n\t}\n\tresult := append([]pinnedDocRef{}, remaining[:index]...)\n\tresult = append(result, refs...)\n\tresult = append(result, remaining[index:]...)\n\tif slices.Equal(stored.Docs, result) {\n\t\treturn nil\n\t}\n\tstored.Docs = result\n\treturn writePinnedDocs(stored)\n}\n\n// 源文档删除或跨笔记本移动后维护入口，异常配置保留原文件并记录错误。\nfunc maintainPinnedDocs(ids map[string]bool, notebook, destination string) {\n\tpinnedDocsLock.Lock()","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/pinned_docs.go#L205-L241","documentation":"When pinning with a targetID, UpdatePinnedDocs searches the remaining (already-pinned) refs for the target to compute the insertion position. If targetID is not currently pinned (or was itself removed by this same update because it is in ids), no insertion anchor exists and the kernel returns this error instead of guessing a position.","triggerScenarios":"Calling UpdatePinnedDocs(ids, \"pin\", targetID, after) where targetID is not in the current pinned list, is misspelled, was deleted, or is included in ids itself so it was filtered out of `remaining`.","commonSituations":"Plugin computes the anchor from a stale pinned list after another window changed the order; target doc was just unpinned in the same call (passed in both ids and targetID); UI passes an anchor that was never pinned.","solutions":["Pass a targetID that is currently pinned (fetch the list via GetPinnedDocs first).","Do not include targetID in the ids array of the same call.","Omit targetID (empty string) to append the new pins instead of inserting relative to an anchor.","Refresh the pinned list in the caller after concurrent updates from other windows."],"exampleFix":"// before\nmodel.UpdatePinnedDocs([]string{newID, targetID}, \"pin\", targetID, true)\n// after\nmodel.UpdatePinnedDocs([]string{newID}, \"pin\", targetID, true)","handlingStrategy":"validation","validationCode":"pinned := model.GetPinnedDocs()\nanchorOK := false\nfor _, d := range pinned {\n    if d.ID == targetID { anchorOK = true; break }\n}\nif !anchorOK { targetID = \"\" } // fall back to append","typeGuard":null,"tryCatchPattern":"if err := model.UpdatePinnedDocs(ids, \"pin\", targetID, after); err != nil {\n    if strings.Contains(err.Error(), \"pinned target\") {\n        // re-fetch pinned list and retry with empty targetID (append)\n    }\n}","preventionTips":["Fetch the current pinned list immediately before anchored inserts","Never include targetID in the ids array of the same call","Fall back to append (empty targetID) when the anchor cannot be verified","Refresh UI state after concurrent pin changes from other windows"],"tags":["pinned-docs","invalid-argument","ordering"],"backgroundTag":"resource-not-found","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"}