siyuan-note/siyuan · error

: Vault root is a symbolic link or reparse point (wrapped…

Error message

%w: Vault root is a symbolic link or reparse point (wrapped: Obsidian Vault path is unsafe)

What it means

validateObsidianVaultRoot wraps errObsidianVaultUnsafePath with 'Vault root is a symbolic link or reparse point' when the vault root itself is a symlink (ModeSymlink) or resolves through a link/reparse point (isObsidianResolvedLink). SiYuan refuses such roots to prevent path-traversal and data-safety issues during import.

Solutions

  1. Select the real (physical) vault directory directly instead of the symlink/junction
  2. Remove the symlink and pass the resolved target path (e.g. from realpath / fs.realpathSync)
  3. If a junction is required for organization, point the import at the junction target, not the link

Example fix

// before
analyzeVault({ localPath: '/Users/me/Documents/MyVault' }); // symlink
// after
const real = fs.realpathSync('/Users/me/Documents/MyVault');
analyzeVault({ localPath: real });
Defensive patterns

Strategy: validation

Validate before calling

const real = await fs.promises.realpath(vaultPath);
const st = await fs.promises.lstat(vaultPath);
if (st.isSymbolicLink()) throw new Error('Select the real vault folder, not a symlink');
await analyzeVault({ localPath: real });

Type guard

async function isRealDir(p) { try { return (await fs.promises.lstat(p)).isDirectory() && (await fs.promises.realpath(p)) === p; } catch { return false; } }

Try / catch

try { await analyzeVault(opts); } catch (e) { if (isVaultUnsafe(e) && /symbolic link/.test(String(e))) { opts.localPath = await fs.promises.realpath(opts.localPath); return analyzeVault(opts); } throw e; }

Prevention

When it happens

Trigger: Selecting a vault directory that is a symlink, a Windows junction, or a macOS Finder alias; vaults placed under symlinked folders like Dropbox/ iCloud aliases that report as links.

Common situations: Users keeping the real vault elsewhere and linking it into Documents; Windows library junctions; synced-folder indirection setups.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/858adc403abf0fae. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/import_obsidian.go:574

}

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) {
			return "", errObsidianVaultConfigMissing
		}
		return "", fmt.Errorf("%w: read Vault config directory: %v", errObsidianVaultUnreadable, statErr)
	}
	if !configInfo.IsDir() || configInfo.Mode()&os.ModeSymlink != 0 || isObsidianResolvedLink(configPath) {
		return "", errObsidianVaultConfigMissing

View on GitHub (pinned to 9f775e8a12)