siyuan-note/siyuan · error

Obsidian Vault path is not a directory

Error message

Obsidian Vault path is not a directory

What it means

A sentinel error returned when the Vault root path exists but is not a directory (e.g. it is a regular file). It is produced directly in validateObsidianVaultRoot at the `!info.IsDir()` check, without additional wrapping. The obsidianVaultErrorLanguage helper maps it to i18n key 337 for user-facing display.

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 251596fc0d)

Solutions

  1. Point localPath at the Vault root folder (the directory containing the .obsidian subfolder), not at a file inside or outside it.
  2. If the Vault arrived as a ZIP, extract it to a directory first, then pass that directory's path.
  3. In the UI file picker, ensure a folder/directory is selected, not an individual file.

Example fix

// before: user passes a file path
localPath := "/home/user/MyVault.md"
// -> errObsidianVaultNotDirectory

// after: pass the directory containing .obsidian
localPath := "/home/user/MyVault"
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the path is a directory before calling analysis
func ensureIsDir(p string) error {
    info, err := os.Stat(p)
    if err != nil { return err }
    if !info.IsDir() { return errors.New("path is not a directory") }
    return nil
}

Try / catch

if _, err := model.StartObsidianVaultAnalysis(localPath); err != nil {
    if errors.Is(err, errObsidianVaultNotDirectory) {
        return errors.New("please select the Vault folder, not a file")
    }
}

Prevention

When it happens

Trigger: POST /api/import/startObsidianVaultAnalysis is called with a localPath that resolves to a file (a .md file, a .zip archive, etc.) rather than a directory. os.Lstat succeeds but info.IsDir() is false.

Common situations: User selects a single Markdown file or a ZIP export instead of the Vault folder in the file picker; a path typo points at a file with the same base name; the Vault was exported as a single archive that has not been unpacked.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/01657a5e26e18571. Report an issue: GitHub.