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

  1. Prompt the user to select the Obsidian vault folder and pass its absolute path
  2. Check the caller variable for emptiness before invoking the API
  3. 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

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


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)