{"record":{"id":"ff97fbe904c81512","repo":"siyuan-note/siyuan","slug":"can-not-remove-s-caused-by-it-is-not-a-dir","errorCode":null,"errorMessage":"can not remove [%s] caused by it is not a dir","messagePattern":"can not remove \\[(.+?)\\] caused by it is not a dir","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/mount.go","lineNumber":215,"sourceCode":"\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)\n\t}\n\n\t// 删目录前固定加密状态，确保后续历史、资源和索引清理始终使用同一个安全边界。\n\tisEncrypted := IsEncryptedBox(boxID)\n\tif isEncrypted {\n\t\t// 加密索引先持有生命周期租约再获取索引锁，因此删除也必须先结束生命周期，保持锁顺序一致。\n\t\tunmount0(boxID)\n\t}\n\n\tdatabaseIndexDataLock.Lock()\n\tdefer databaseIndexDataLock.Unlock()\n\tcreateDocLock.Lock()\n\tdefer createDocLock.Unlock()\n\tif !isEncrypted {\n\t\tunmount0(boxID)\n\t}\n\tClearRichClipboardBox(boxID)\n\tif !isEncrypted {","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/mount.go#L197-L233","documentation":"Returned by RemoveBox (kernel/model/mount.go:215) when the notebook path exists on disk (filelock.IsExist) but is not a directory (gulu.File.IsDir returns false). SiYuan notebooks must be directories under util.DataDir; a regular file at the notebook path is treated as an invalid state and removal is refused.","triggerScenarios":"Calling /api/notebook/removeNotebook where <workspace>/data/<boxID> resolves to a file (someone created a file with the same name as a notebook ID), or a symlink/pipe/device was placed there manually.","commonSituations":"Manual file-system tampering, a partial/corrupted workspace, or another tool that wrote a file named like a notebook ID under data/.","solutions":["Inspect util.DataDir/<boxID> on disk; if it is a stray file, move or delete it manually (after confirming it is not a notebook dir that lost its directory bit), then retry the remove.","If it is a symlink that got mangled, restore or remove the link and retry.","Avoid naming unrelated files with 22-character base32 IDs that match IsNodeIDPattern to prevent future collisions."],"exampleFix":"# before: file blocking removal\n$ ls data/<boxID>  # regular file\n# after: remove the stray file, then retry the API\n$ rm data/<boxID>\n# retry: POST /api/notebook/removeNotebook  {\"notebook\":\"<boxID>\"}","handlingStrategy":"validation","validationCode":"// Pre-check the path before calling RemoveBox.\np := filepath.Join(util.DataDir, boxID)\nif info, err := os.Stat(p); err == nil && !info.IsDir() {\n    return fmt.Errorf(\"%s is not a directory; resolve on disk first\", p)\n}\nreturn model.RemoveBox(boxID)","typeGuard":null,"tryCatchPattern":"if err := model.RemoveBox(boxID); err != nil {\n    if strings.Contains(err.Error(), \"it is not a dir\") {\n        // filesystem-level repair required, not a retry candidate\n        log.Warn(\"notebook path is not a directory; manual cleanup needed: %s\", boxID)\n    }\n    return err\n}","preventionTips":["Never create files under data/ that share a notebook ID format.","Treat this error as a data-integrity signal — do not auto-retry without inspecting the path."],"tags":["notebook","filesystem","remove","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}