siyuan-note/siyuan · error
: normalize Vault path: (wrapped: Obsidian Vault is…
Error message
%w: normalize Vault path: %v (wrapped: Obsidian Vault is unreadable)
What it means
validateObsidianVaultRoot wraps errObsidianVaultUnreadable with 'normalize Vault path: %v' when filepath.Abs(filepath.Clean(localPath)) fails. filepath.Abs essentially only fails when resolving the working directory fails (e.g. the process cwd was deleted), so this indicates a broken process environment rather than a user mistake.
Solutions
- Restart the SiYuan kernel from a valid existing working directory
- Pass an absolute vault path instead of a relative one so Abs has less to resolve
- Check the mount/disk holding the kernel's cwd and restore it
Example fix
// before
analyzeVault({ localPath: '../vaults/notes' });
// after
analyzeVault({ localPath: '/home/user/Documents/vaults/notes' }); // absolute path Defensive patterns
Strategy: fallback
Validate before calling
try { process.cwd(); } catch { alert('Working directory is gone; restart the kernel'); } Try / catch
try { await analyzeVault(opts); } catch (e) { if (isVaultUnreadable(e) && /normalize Vault path/.test(String(e))) restartKernelThenRetry(); else throw e; } Prevention
- Launch the kernel from a stable, existing directory
- Always pass absolute vault paths
- Avoid deleting or unmounting the directory the kernel was started from
When it happens
Trigger: Calling the analyze/import API when the kernel process's current working directory has been removed, making Getwd (used by filepath.Abs for relative paths) fail.
Common situations: Kernel launched from a directory that was deleted or unmounted afterwards; containers with volatile workdirs; running on a removed network share.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- asset path contains an unresolved symbolic link
- asset path resolves outside notebook assets directory
- Obsidian Vault is unreadable
- : read Vault root: (wrapped: Obsidian Vault is unreadable)
- 345
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/e08e898a1501fc44.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/import_obsidian.go:564
ret.ImportAssets[key] = asset
ret.Analysis.ImportableAssetCount++
ret.Analysis.ImportableAssetSize += asset.Source.Size
}
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)
}View on GitHub (pinned to 9f775e8a12)