siyuan-note/siyuan · error
: path is empty (wrapped: Obsidian Vault is unreadable)
Error message
%w: path is empty (wrapped: Obsidian Vault is unreadable)
What it means
validateObsidianVaultRoot returns a wrapped errObsidianVaultUnreadable when the supplied Vault localPath is empty or whitespace-only. This is the first guard before any filesystem access, ensuring a meaningful path is analyzed.
Solutions
- Prompt the user to select the Obsidian vault folder and pass its absolute path
- Check the caller variable for emptiness before invoking the API
- Restore the persisted vault path in config if it was lost
Example fix
// before
await analyzeVault({ localPath: cfg.vaultPath }); // may be ''
// after
if (!cfg.vaultPath || !cfg.vaultPath.trim()) {
cfg.vaultPath = await promptPickVaultFolder();
}
await analyzeVault({ localPath: cfg.vaultPath }); Defensive patterns
Strategy: validation
Validate before calling
const p = (cfg.vaultPath || '').trim();
if (!p) throw new Error('Vault path is required');
await analyzeVault({ localPath: p }); Type guard
function hasVaultPath(v) { return typeof v === 'string' && v.trim().length > 0; } Try / catch
try { await analyzeVault(opts); } catch (e) { if (isVaultUnreadable(e) && !opts.localPath?.trim()) { opts.localPath = await pickFolder(); return analyzeVault(opts); } throw e; } Prevention
- Require folder selection in the UI before enabling Analyze
- Trim and validate the path at the call site
- Persist the vault path only after a successful selection
When it happens
Trigger: Calling the Obsidian analyze/import API with localPath "" or " " — e.g. an unset config field, an empty form input, or a variable that failed to populate before the call.
Common situations: User clicked Analyze without choosing a folder; automated scripts passing an uninitialized variable; config migration dropped the stored vault path.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- asset path is required
- document IDs are required
- invalid attribute view source
- invalid package type
- invalid paste row count
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/4a169ac7be51f916.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/import_obsidian.go:560
}
ret.Analysis.Warnings = append(ret.Analysis.Warnings, asset.Source.RelPath)
continue
}
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)
}View on GitHub (pinned to 9f775e8a12)