{"record":{"id":"f4930584fc4431d6","repo":"siyuan-note/siyuan","slug":"document-ids-are-required","errorCode":null,"errorMessage":"document IDs are required","messagePattern":"document IDs are required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/pinned_docs.go","lineNumber":160,"sourceCode":"\t\t\tdoc.Name = Conf.Language(16)\n\t\t}\n\t\tif ref.ID == boxID {\n\t\t\tdoc.SubFileCount = BoxDocSubFileCount(boxID)\n\t\t} else {\n\t\t\tdoc.SubFileCount, err = visibleDocCount(boxID, strings.TrimSuffix(bt.Path, \".sy\"), box.docIAL, nil)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tret = append(ret, doc)\n\t}\n\treturn\n}\n\n// 根层顺序独立于源文档顺序，按相对位置更新以保留其他窗口新增的入口。\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","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/pinned_docs.go#L142-L178","documentation":"Validation guard at the start of UpdatePinnedDocs: fires when the ids slice is empty or nil. Updating pinned-document order or pin state requires at least one document ID; callers passing an empty list (e.g. no pinned entries to reorder) hit this before the action validity check.","triggerScenarios":"Calling the pinned-docs update API (kernel API / pin action from the file tree) with an empty ids array — e.g. frontend bug sending no selection, or a plugin/script calling the endpoint with no documents selected.","commonSituations":"Automated scripts or plugins invoking the API with an empty list; frontend state where the selected document list was cleared before submit; race where documents were unpinned by another window before the call.","solutions":["Ensure at least one document ID is passed in the ids parameter of the update call","Fix the frontend/plugin caller to guard against empty selections before invoking the API","Re-select the documents in the file tree and retry the pin/unpin action"],"exampleFix":"// before (caller)\nawait fetchPost(\"/api/filetree/updatePinnedDocs\", {ids: [], action: \"pin\"});\n// after\nif (!ids.length) return; // nothing selected\nawait fetchPost(\"/api/filetree/updatePinnedDocs\", {ids, action: \"pin\"});","handlingStrategy":"validation","validationCode":"// Caller-side guard before invoking the update API\nif ids == nil || len(ids) == 0 {\n\treturn errors.New(\"select at least one document to pin/unpin\")\n}","typeGuard":null,"tryCatchPattern":"err := UpdatePinnedDocs(ids, \"pin\", \"\", false)\nif err != nil && strings.Contains(err.Error(), \"document IDs are required\") {\n\t// surface a user-facing 'no documents selected' hint\n}","preventionTips":["Guard UI actions so pin/unpin only fires with a selection","Deduplicate and filter empty strings out of ids before calling","In plugins/scripts, assert ids non-empty before the API call","Log the payload when the API rejects input to catch frontend regressions"],"tags":["go","api","validation","argument"],"backgroundTag":"empty-required-field","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"}