{"record":{"id":"4a169ac7be51f916","repo":"siyuan-note/siyuan","slug":"w-path-is-empty-wrapped-obsidian-vault-is-unreadable","errorCode":null,"errorMessage":"%w: path is empty (wrapped: Obsidian Vault is unreadable)","messagePattern":"%w: path is empty \\(wrapped: Obsidian Vault is unreadable\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":560,"sourceCode":"\t\t\t}\n\t\t\tret.Analysis.Warnings = append(ret.Analysis.Warnings, asset.Source.RelPath)\n\t\t\tcontinue\n\t\t}\n\t\tret.ImportAssets[key] = asset\n\t\tret.Analysis.ImportableAssetCount++\n\t\tret.Analysis.ImportableAssetSize += asset.Source.Size\n\t}\n\tret.Analysis.UnreferencedFileCount = len(ret.ImportAssets) - len(ret.ReferencedAssets)\n\tif ret.Analysis.UnreferencedFileCount < 0 {\n\t\tret.Analysis.UnreferencedFileCount = 0\n\t}\n\tprogress(100, \"Analysis completed\")\n\treturn ret, nil\n}\n\nfunc validateObsidianVaultRoot(localPath string) (string, error) {\n\tif strings.TrimSpace(localPath) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"%w: path is empty\", errObsidianVaultUnreadable)\n\t}\n\tabs, err := filepath.Abs(filepath.Clean(localPath))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%w: normalize Vault path: %v\", errObsidianVaultUnreadable, err)\n\t}\n\tinfo, err := os.Lstat(abs)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%w: read Vault root: %v\", errObsidianVaultUnreadable, err)\n\t}\n\tif !info.IsDir() {\n\t\treturn \"\", errObsidianVaultNotDirectory\n\t}\n\tif info.Mode()&os.ModeSymlink != 0 || isObsidianResolvedLink(abs) {\n\t\treturn \"\", fmt.Errorf(\"%w: Vault root is a symbolic link or reparse point\", errObsidianVaultUnsafePath)\n\t}\n\tif util.IsSensitivePath(abs) {\n\t\treturn \"\", fmt.Errorf(\"%w: selected Vault path is sensitive\", errObsidianVaultUnsafePath)\n\t}","sourceCodeStart":542,"sourceCodeEnd":578,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/import_obsidian.go#L542-L578","documentation":"validateObsidianVaultRoot returns a wrapped errObsidianVaultUnreadable when the supplied Vault localPath is empty or whitespace-only. This is the first guard before any filesystem access, ensuring a meaningful path is analyzed.","triggerScenarios":"Calling the Obsidian analyze/import API with localPath \"\" or \"   \" — e.g. an unset config field, an empty form input, or a variable that failed to populate before the call.","commonSituations":"User clicked Analyze without choosing a folder; automated scripts passing an uninitialized variable; config migration dropped the stored vault path.","solutions":["Prompt the user to select the Obsidian vault folder and pass its absolute path","Check the caller variable for emptiness before invoking the API","Restore the persisted vault path in config if it was lost"],"exampleFix":"// before\nawait analyzeVault({ localPath: cfg.vaultPath }); // may be ''\n// after\nif (!cfg.vaultPath || !cfg.vaultPath.trim()) {\n  cfg.vaultPath = await promptPickVaultFolder();\n}\nawait analyzeVault({ localPath: cfg.vaultPath });","handlingStrategy":"validation","validationCode":"const p = (cfg.vaultPath || '').trim();\nif (!p) throw new Error('Vault path is required');\nawait analyzeVault({ localPath: p });","typeGuard":"function hasVaultPath(v) { return typeof v === 'string' && v.trim().length > 0; }","tryCatchPattern":"try { await analyzeVault(opts); } catch (e) { if (isVaultUnreadable(e) && !opts.localPath?.trim()) { opts.localPath = await pickFolder(); return analyzeVault(opts); } throw e; }","preventionTips":["Require folder selection in the UI before enabling Analyze","Trim and validate the path at the call site","Persist the vault path only after a successful selection"],"tags":["obsidian","validation","empty-path","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"}