{"record":{"id":"a19632856e32a71c","repo":"siyuan-note/siyuan","slug":"invalid-box-id-s","errorCode":null,"errorMessage":"invalid box ID [%s]","messagePattern":"invalid box ID \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/box_doc.go","lineNumber":130,"sourceCode":"\tvar bt *treenode.BlockTree\n\tif \"\" == boxID {\n\t\tbt = treenode.GetBlockTree(id)\n\t} else {\n\t\tbt = treenode.GetBlockTreeInBox(id, boxID)\n\t}\n\treturn nil != bt && IsBoxDoc(bt.BoxID, bt.RootID)\n}\n\nfunc EnsureBoxDoc(boxID string) (boxDocID string, err error) {\n\tcreateDocLock.Lock()\n\tdefer createDocLock.Unlock()\n\treturn ensureBoxDoc0(boxID)\n}\n\n// ensureBoxDoc0 的调用方必须持有 createDocLock。\nfunc ensureBoxDoc0(boxID string) (boxDocID string, err error) {\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn \"\", fmt.Errorf(\"invalid box ID [%s]\", boxID)\n\t}\n\n\tbox := Conf.GetBox(boxID)\n\tif nil == box {\n\t\treturn \"\", ErrBoxNotFound\n\t}\n\n\tif !IsBoxDocEnabled() {\n\t\treturn\n\t}\n\tboxDocID = boxID\n\n\tboxDocID, err = findBoxDoc(box)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tcreated, changed := false, false\n\tif \"\" == boxDocID {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/box_doc.go#L112-L148","documentation":"First guard in ensureBoxDoc0 (kernel/model/box_doc.go): the boxID argument must satisfy ast.IsNodeIDPattern before anything else runs. Callers are notebook mount (kernel/model/mount.go:475), config-driven ensure (kernel/model/conf.go:1311) and repo sync apply (kernel/model/repository.go:2509). The error signals a malformed notebook identifier in persisted state or a bad API argument, not a runtime failure.","triggerScenarios":"A notebook folder under data/ whose name is not a 20-char node ID (hand-created folder) reaches mount; a corrupted conf.json notebooks list; a repo snapshot referencing a box with a garbage ID during sync apply; calling notebook APIs with arbitrary strings instead of IDs from /api/notebook/lsNotebooks.","commonSituations":"Users creating notebook folders manually in the file manager; workspace files touched by external tools; damaged sync snapshots; scripts calling /api/notebook/* with made-up IDs.","solutions":["Verify the notebook ID passed in: it must match ^\\d{14}-[a-z0-9]{7}$","If a hand-created folder caused it, rename it to a valid unique node ID or remove it and create the notebook through SiYuan's UI/API","If conf.json lists a malformed notebook, remove that entry and restart the kernel","For repo sync, inspect the incoming snapshot's notebook IDs before applying"],"exampleFix":"// before\nboxID := \"my-notebook\"\nensureBoxDoc0(boxID)\n// after\nboxID := \"20240601120000-newbox01\"\nensureBoxDoc0(boxID)","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Go predicate mirroring ast.IsNodeIDPattern\nvar nodeIDRe = regexp.MustCompile(`^\\d{14}-[a-z0-9]{7}$`)\n\nfunc isValidNodeID(id string) bool {\n\treturn nodeIDRe.MatchString(id)\n}\n\n// use before the call\nif !isValidNodeID(boxID) {\n\treturn fmt.Errorf(\"refusing to ensure box doc: bad notebook ID %q\", boxID)\n}\n_, _ = model.EnsureBoxDoc(boxID)","tryCatchPattern":"if _, err := model.EnsureBoxDoc(boxID); err != nil {\n\tif strings.Contains(err.Error(), \"invalid box ID\") {\n\t\t// input problem: fix the caller, do not retry\n\t}\n\treturn err\n}","preventionTips":["Always take notebook IDs from /api/notebook/lsNotebooks output","Create notebooks via the API, never by adding folders under data/","Validate repo/sync inputs that carry box IDs before applying snapshots"],"tags":["siyuan","box-doc","notebook","input-validation","id-format"],"backgroundTag":"invalid-identifier-format","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}