{"record":{"id":"0ba9ad4ce1d7b2d2","repo":"siyuan-note/siyuan","slug":"can-not-remove-s-caused-by-it-is-a-reserved-fil","errorCode":null,"errorMessage":"can not remove [%s] caused by it is a reserved file","messagePattern":"can not remove \\[(.+?)\\] caused by it is a reserved file","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/mount.go","lineNumber":197,"sourceCode":"\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}\n\tisUserGuide := IsUserGuide(boxID)\n\tlocalPath := filepath.Join(util.DataDir, boxID)\n\tif !filelock.IsExist(localPath) {\n\t\tforgetRuntimeNormalBox(boxID)\n\t\tremoveMasterPasswordMigrationBox(boxID)\n\t\treturn\n\t}\n\tif !gulu.File.IsDir(localPath) {\n\t\treturn fmt.Errorf(\"can not remove [%s] caused by it is not a dir\", boxID)","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/mount.go#L179-L215","documentation":"RemoveBox refuses to delete a notebook whose ID collides with a reserved filename: util.IsReservedFilename(boxID) is true, returning fmt.Errorf('can not remove [%s] caused by it is a reserved file', boxID). This protects SiYuan's own data-directory layout (names like data, temp, conf, .siyuan, assets, storage, etc.) from being removed via the notebook API.","triggerScenarios":"Calling RemoveBox with an ID equal to a reserved filename. Normally unreachable because real notebook IDs are generated by ast.NewNodeID and never match reserved names, but a crafted or mistaken ID that passed IsNodeIDPattern (797) yet equals a reserved name would hit this. It is a defense-in-depth guard before any destructive action.","commonSituations":"Direct/malformed API calls with hand-crafted IDs; a future reserved name added that collides with an existing ID (extremely unlikely given the ID format); integration tests using IDs that happen to match reserved names.","solutions":["Only call RemoveBox with IDs returned by CreateBox/ListNotebooks; never craft IDs manually.","If you maintain a reserved-name list mirror, check util.IsReservedFilename before invoking and reject.","Treat this error as a programming bug, not a user action — fix the caller to use the real notebook ID."],"exampleFix":"// before\nmodel.RemoveBox(\"conf\") // reserved filename -> rejected\n\n// after: use the real notebook ID from ListNotebooks\nfor _, nb := range mustListNotebooks() {\n    if nb.ID == targetID { model.RemoveBox(targetID); break }\n}","handlingStrategy":"validation","validationCode":"if util.IsReservedFilename(boxID) {\n    return fmt.Errorf(\"%q is a reserved name; pass a real notebook ID\", boxID)\n}","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Never craft notebook IDs manually; use IDs from CreateBox/ListNotebooks.","Mirror the reserved-name check at API trust boundaries."],"tags":["notebook","mount","remove","reserved","safety","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}