siyuan-note/siyuan · error

Obsidian Vault is unreadable: path is empty

Error message

Obsidian Vault is unreadable: path is empty

What it means

A wrapped variant of errObsidianVaultUnreadable (line 559-560): returned by validateObsidianVaultRoot when the localPath argument, after trimming whitespace, is the empty string. This is the first guard in root validation, catching a missing or blank path before any filesystem access. It wraps the unreadable sentinel with the suffix ': path is empty'.

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

Solutions

  1. Provide a non-empty directory path as localPath in the request body.
  2. Validate on the client that the path field is non-empty before submitting.
  3. Check the JSON request body includes a 'localPath' key with a real filesystem path.
  4. If using the UI, ensure the folder picker completed successfully before clicking import.

Example fix

// before
POST /api/import/startObsidianVaultAnalysis { "localPath": "" }
// -> "Obsidian Vault is unreadable: path is empty"

// after
POST /api/import/startObsidianVaultAnalysis { "localPath": "/home/user/MyVault" }
Defensive patterns

Strategy: validation

Validate before calling

// Ensure a non-empty path before calling the API
if strings.TrimSpace(localPath) == "" {
    return errors.New("Vault path must not be empty")
}

Try / catch

if _, err := model.StartObsidianVaultAnalysis(localPath); err != nil {
    if errors.Is(err, errObsidianVaultUnreadable) && strings.Contains(err.Error(), "path is empty") {
        return errors.New("no Vault folder was provided; please select a folder")
    }
}

Prevention

When it happens

Trigger: POST /api/import/startObsidianVaultAnalysis with localPath set to "", a whitespace-only string, or null/undefined coerced to empty. The trim check at line 559 fires immediately.

Common situations: The frontend file picker returned no selection; the path field was left blank in a scripted API call; a null value was JSON-encoded as empty string; a UI bug submitted the form without a selected folder.

Related errors


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