{"record":{"id":"f476d6e602718b63","repo":"siyuan-note/siyuan","slug":"invalid-notebook-id","errorCode":null,"errorMessage":"invalid notebook ID","messagePattern":"invalid notebook ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/mount.go","lineNumber":269,"sourceCode":"\t}\n\treturn\n}\n\nfunc collectBoxDeletedAttributeViewBlocks(boxID string) (ret map[string]map[string]struct{}, err error) {\n\trootIDs := treenode.GetRootBlockIDsByBoxID(boxID)\n\tif 1 > len(rootIDs) {\n\t\treturn map[string]map[string]struct{}{}, nil\n\t}\n\tboundAVIDs, err := sql.QueryBoundBlockAVIDsInBox(nil, rootIDs, boxID)\n\tif nil != err {\n\t\treturn nil, err\n\t}\n\treturn groupDeletedAttributeViewBlocks(boundAVIDs), nil\n}\n\nfunc RemoveBox(boxID string) (err error) {\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn errors.New(\"invalid notebook ID\")\n\t}\n\tif _, loaded := boxLock.LoadOrStore(boxID, true); loaded {\n\t\terr = errors.New(Conf.language(239))\n\t\treturn\n\t}\n\tdefer boxLock.Delete(boxID)\n\n\tif util.IsReservedFilename(boxID) {\n\t\treturn fmt.Errorf(\"can not remove [%s] caused by it is a reserved file\", boxID)\n\t}\n\n\tFlushTxQueue()\n\tsql.FlushQueue()\n\t// 索引和笔记本目录删除后无法再读取 custom-avs，需提前收集；实际删除成功后再清理绑定行。\n\tdeletedAttrViewBlockIDs, err := collectBoxDeletedAttributeViewBlocks(boxID)\n\tif nil != err {\n\t\treturn fmt.Errorf(\"query database-bound blocks in notebook [%s] failed: %w\", boxID, err)\n\t}","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/mount.go#L251-L287","documentation":"RemoveBox validates that boxID matches ast.IsNodeIDPattern (a SiYuan node-ID format: 14-digit timestamp + 6 random chars, e.g. 20240102150405-abcdefg) before doing any work. If the ID is not a valid node ID it returns the literal error \"invalid notebook ID\". This guards the notebook-removal API against malformed or arbitrary path-like identifiers.","triggerScenarios":"Calling the kernel API /api/notebook/removeNotebook (or RemoveBox directly) with an ID that is not a 20-character node ID — e.g. a notebook name, a relative path, an empty string, an old-style ID, or a value copied from a different field.","commonSituations":"Scripting against the HTTP API with the notebook name instead of its ID; stale client code holding IDs from a pre-rename dataset; plugin code concatenating path components into the ID; trimmed/truncated IDs from logs or config files.","solutions":["Fetch the correct notebook ID first via /api/notebook/lsNotebooks and pass the id field exactly (20 chars, format tttttttttttttt-xxxxxxx)","Check the value for stray whitespace, quotes, or path separators; trim or re-read it from the notebooks list response","If the ID comes from stored config, verify the notebook still exists and regenerate the reference from lsNotebooks"],"exampleFix":"// before: using notebook name as ID\nfetchPost(\"/api/notebook/removeNotebook\", {notebook: \"My Notes\"})\n// after: resolve ID first\nconst notebooks = await fetchPost(\"/api/notebook/lsNotebooks\", {})\nconst box = notebooks.notebooks.find(nb => nb.name === \"My Notes\")\nawait fetchPost(\"/api/notebook/removeNotebook\", {notebook: box.id})","handlingStrategy":"validation","validationCode":"var nodeIDRe = regexp.MustCompile(`^\\d{14}-[0-9a-z]{7}$`)\nfunc isValidNotebookID(id string) bool { return nodeIDRe.MatchString(id) }\n// call removeNotebook only if isValidNotebookID(notebookID)","typeGuard":null,"tryCatchPattern":"err := removeNotebook(boxID)\nif err != nil && err.Error() == \"invalid notebook ID\" {\n    // re-resolve the ID via lsNotebooks and retry once\n}","preventionTips":["Always obtain notebook IDs from /api/notebook/lsNotebooks, never from names or paths","Validate the 14-7 pattern (id-time-rand) before sending removal requests","Trim whitespace from IDs read from logs or config before passing them"],"tags":["validation","notebook","api","identifier"],"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"}