siyuan-note/siyuan · error
: read Vault root: (wrapped: Obsidian Vault is unreadable)
Error message
%w: read Vault root: %v (wrapped: Obsidian Vault is unreadable)
What it means
validateObsidianVaultRoot wraps errObsidianVaultUnreadable with 'read Vault root: %v' when os.Lstat(abs) fails — the path does not exist or cannot be stat'ed (permission, IO error). The underlying OS error is included in the message.
Solutions
- Verify the path exists (ls / dir) and correct typos, then re-run the analysis
- Remount the drive/share hosting the vault
- Fix permissions: chmod/chown, or grant the app Full Disk/folder access (macOS System Settings)
- Pick the vault folder again via the folder picker to store a fresh path
Example fix
// before
analyzeVault({ localPath: '/Volumes/USB/vault' }); // drive unplugged
// after
if (fs.existsSync(vaultPath)) {
analyzeVault({ localPath: vaultPath });
} Defensive patterns
Strategy: validation
Validate before calling
const st = await fs.promises.stat(vaultPath).catch(() => null);
if (!st || !st.isDirectory()) throw new Error(`Vault path missing: ${vaultPath}`); Type guard
async function isReadableDir(p) { try { return (await fs.promises.stat(p)).isDirectory(); } catch { return false; } } Try / catch
try { await analyzeVault(opts); } catch (e) { if (isVaultUnreadable(e) && /read Vault root/.test(String(e))) { showPathFixUi(opts.localPath); } else throw e; } Prevention
- Re-select the vault if it was moved or renamed
- Remount network/USB drives before importing
- On macOS grant the app Full Disk access; on Windows check ACLs
When it happens
Trigger: Analyzing a vault path that was deleted/renamed, contains a typo, sits on an unmounted drive, or is unreadable due to permissions (including macOS TCC blocking access to folders).
Common situations: Vault moved since last use; network drive disconnected; Windows OneDrive folder offline; macOS 'Files and Folders' permission denied for the kernel app.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Conf.Language(14) (copy resource failed: )
- create AI editor actions directory failed
- create conf dir failed
- create import dir failed:
- create import dir failed
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/68bed470ad0c05f4.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/import_obsidian.go:568
ret.Analysis.UnreferencedFileCount = len(ret.ImportAssets) - len(ret.ReferencedAssets)
if ret.Analysis.UnreferencedFileCount < 0 {
ret.Analysis.UnreferencedFileCount = 0
}
progress(100, "Analysis completed")
return ret, nil
}
func validateObsidianVaultRoot(localPath string) (string, error) {
if strings.TrimSpace(localPath) == "" {
return "", fmt.Errorf("%w: path is empty", errObsidianVaultUnreadable)
}
abs, err := filepath.Abs(filepath.Clean(localPath))
if err != nil {
return "", fmt.Errorf("%w: normalize Vault path: %v", errObsidianVaultUnreadable, err)
}
info, err := os.Lstat(abs)
if err != nil {
return "", fmt.Errorf("%w: read Vault root: %v", errObsidianVaultUnreadable, err)
}
if !info.IsDir() {
return "", errObsidianVaultNotDirectory
}
if info.Mode()&os.ModeSymlink != 0 || isObsidianResolvedLink(abs) {
return "", fmt.Errorf("%w: Vault root is a symbolic link or reparse point", errObsidianVaultUnsafePath)
}
if util.IsSensitivePath(abs) {
return "", fmt.Errorf("%w: selected Vault path is sensitive", errObsidianVaultUnsafePath)
}
workspace, _ := filepath.Abs(filepath.Clean(util.WorkspaceDir))
if sameObsidianPath(abs, workspace) || gulu.File.IsSubPath(workspace, abs) || gulu.File.IsSubPath(abs, workspace) {
return "", fmt.Errorf("%w: Vault root and SiYuan workspace contain each other", errObsidianVaultUnsafePath)
}
configPath := filepath.Join(abs, ".obsidian")
configInfo, statErr := os.Lstat(configPath)
if statErr != nil {
if os.IsNotExist(statErr) {View on GitHub (pinned to 9f775e8a12)