siyuan-note/siyuan · error
Obsidian Vault path is not a directory
Error message
Obsidian Vault path is not a directory
What it means
`errObsidianVaultNotDirectory` is returned by `validateObsidianVaultRoot` when the given path exists but `stat` shows it is not a directory. It maps to i18n key 337 in `obsidianVaultErrorLanguage`, so the user sees a localized "Vault path is not a directory" message.
Solutions
- Pass the Vault root directory (the folder containing `.obsidian/`), not a file inside it
- Fix path quoting/escaping so the full directory path is passed
- Recreate the directory if the vault folder was replaced by a file
Example fix
// before
validateObsidianVaultRoot("/home/user/MyVault/README.md") // errObsidianVaultNotDirectory
// after
validateObsidianVaultRoot("/home/user/MyVault") Defensive patterns
Strategy: validation
Validate before calling
const stat = await fs.stat(vaultPath);
if (!stat.isDirectory()) {
throw new Error(`'${vaultPath}' is a file, not a directory`);
} Type guard
function isDirectoryPath(stat: { isDirectory(): boolean }): boolean {
return stat.isDirectory();
} Try / catch
try {
await api.importObsidianVault(vaultPath);
} catch (e) {
if (isVaultNotDirectory(e)) {
// ask the user to pick the vault folder instead of a note file
}
} Prevention
- Directory pickers should be used instead of file pickers for vault selection
- Verify with fs.stat().isDirectory() before calling the import API
- Watch for quoting bugs that truncate a directory path to a file path
When it happens
Trigger: Calling `ImportObsidianVault` (or its validation endpoint, exercised by `TestObsidianVaultValidationErrors`) with `localPath` pointing to a regular file, symlink to a file, or device node instead of a folder.
Common situations: User pasted the path of a note file (`note.md`) instead of the vault folder; a path was mangled by quoting so it points at a file; an old vault file was restored in place of the directory.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/01657a5e26e18571.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/import_obsidian.go:231
func (err *obsidianUserError) Error() string {
return err.Cause.Error()
}
func (err *obsidianUserError) Unwrap() error {
return err.Cause
}
func newObsidianUserError(detailLanguage int, relPath string, cause error) error {
return &obsidianUserError{DetailLanguage: detailLanguage, RelPath: relPath, Cause: cause}
}
var (
obsidianTasksMu sync.Mutex
obsidianTasks = map[string]*obsidianTask{}
obsidianActive string
errObsidianVaultUnreadable = errors.New("Obsidian Vault is unreadable")
errObsidianVaultNotDirectory = errors.New("Obsidian Vault path is not a directory")
errObsidianVaultUnsafePath = errors.New("Obsidian Vault path is unsafe")
errObsidianVaultConfigMissing = errors.New("Obsidian Vault config directory is missing")
errObsidianVaultMarkdownMissing = errors.New("Obsidian Vault has no readable Markdown")
errObsidianSourceChanged = errors.New("Obsidian source file changed")
obsidianBlockIDPattern = regexp.MustCompile(`(?m)(?:^|[ \t])\^([A-Za-z0-9-]+)[ \t]*$`)
obsidianQuotePattern = regexp.MustCompile(`^((?:[ \t]*>[ \t]?)+)(.*)$`)
obsidianListItemPattern = regexp.MustCompile(`^([ \t]*(?:[-+*]|\d+[.)])[ \t]+)(.*)$`)
obsidianFootnotePattern = regexp.MustCompile(`(?m)\[\^[^\]\r\n]+\]`)
)
func StartObsidianVaultAnalysis(localPath string) (*ObsidianVaultTask, error) {
var replacedTaskID string
obsidianTasksMu.Lock()
if obsidianActive != "" {
if active := obsidianTasks[obsidianActive]; active != nil && !isObsidianTerminalState(active.State) {
if !isObsidianPreImportState(active.State) {
obsidianTasksMu.Unlock()View on GitHub (pinned to 9f775e8a12)