siyuan-note/siyuan · error · errObsidianVaultUnreadable

%w: normalize Vault path: %v

Error message

%w: normalize Vault path: %v

What it means

The provided Vault path could not be converted to an absolute, cleaned path: filepath.Abs/Clean failed (rare, usually working-directory or OS-level problems). The kernel wraps errObsidianVaultUnreadable with 'normalize Vault path: <detail>'.

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 8641553a1f)

Solutions

  1. Pass an absolute vault path so filepath.Abs does not need the process working directory
  2. Verify the kernel's working directory still exists and the workspace is valid; restart the kernel if it was started from a deleted dir
  3. Check the error detail appended to the message for the underlying OS cause
  4. Re-run after fixing the environment (cwd, permissions)

Example fix

// before
analyzeObsidianVault("../my/vault") // relative, fails if cwd gone
// after
analyzeObsidianVault("/Users/me/Documents/my-vault")
Defensive patterns

Strategy: validation

Validate before calling

abs, err := filepath.Abs(vaultPath)
if err != nil { return err }
if _, err := os.Stat(abs); err != nil { return err }

Type guard

null

Try / catch

_, err := AnalyzeObsidianVault(vaultPath)
if err != nil && strings.Contains(err.Error(), "normalize Vault path") {
    // pass absolute path and restart kernel from a valid cwd
    AnalyzeObsidianVault(filepath.Join(wsDir, vaultPath))
}

Prevention

When it happens

Trigger: filepath.Abs(filepath.Clean(localPath)) returning an error in validateObsidianVaultRoot (kernel/model/import_obsidian.go:564), typically because the process working directory could not be determined (getwd failure) while localPath is relative.

Common situations: Kernel started from a deleted working directory; relative vault path supplied while the process cwd is gone; exotic characters/length causing OS path handling failures on some platforms.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/791d9a81e34009c2. Report an issue: GitHub.