siyuan-note/siyuan · error · errObsidianVaultUnreadable
%w: read Vault root: %v
Error message
%w: read Vault root: %v
What it means
validateObsidianVaultRoot wraps errObsidianVaultUnreadable: reading/stat-ing the Vault root directory failed after path normalization (permissions, not a directory, or a broken symlink). The underlying os error is included in the message.
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 8641553a1f)
Solutions
- Confirm the path exists (ls/stat it) and correct the vault path if moved or renamed
- Reconnect the drive or network share hosting the vault
- Grant the SiYuan kernel disk-access permission if OS privacy protection blocks the path
- Re-run the analysis once the path is reachable
Example fix
// before
analyzeObsidianVault("/Volumes/USB/vault") // drive unplugged
// after
if _, err := os.Stat(vaultPath); err != nil {
return errors.New("vault path unreachable: " + err.Error())
}
analyzeObsidianVault(vaultPath) Defensive patterns
Strategy: validation
Validate before calling
if _, err := os.Stat(vaultPath); err != nil {
return fmt.Errorf("vault path unreachable: %w", err)
} Type guard
null
Try / catch
_, err := AnalyzeObsidianVault(vaultPath)
if err != nil && strings.Contains(err.Error(), "read Vault root") {
askUserToReconnectOrRepickVault()
} Prevention
- Verify the vault folder exists before import
- Reconnect external/network drives beforehand
- Grant OS disk-access permissions to the SiYuan app for the vault location
When it happens
Trigger: validateObsidianVaultRoot at kernel/model/import_obsidian.go:568 when the vault root cannot be stat'ed: typo'd path, deleted folder, disconnected drive, or permission denied on a parent directory.
Common situations: Vault folder moved/renamed after being saved in settings; external/USB drive unplugged; network share unavailable; macOS privacy (TCC) blocking kernel access to Documents/Desktop.
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
- read referenced attachment [%s]: %w
- %w: normalize Vault path: %v
- %w: Vault root and SiYuan workspace contain each other
- %w: read Vault config directory: %v
- read Vault directory [%s]: %w
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/18849ecbf2c92329.
Report an issue: GitHub.