siyuan-note/siyuan · error

Obsidian Vault is unreadable

Error message

Obsidian Vault is unreadable

What it means

A sentinel base error used as the wrapping parent for any condition that makes the Obsidian Vault unreadable at a filesystem level. It is never returned bare to the user; instead it is wrapped with a descriptive suffix (e.g. 'path is empty', 'normalize Vault path', 'read Vault root', 'read Vault config directory') via fmt.Errorf with %w. The obsidianVaultErrorLanguage helper maps this sentinel to i18n key 336 for user-facing display. It is the root cause for errors 774, 775, 776, and the config-read failure branch.

Source

Thrown at kernel/model/import_obsidian.go:230

}

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) {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Verify the localPath passed to startObsidianVaultAnalysis is non-empty and points to an existing, readable directory.
  2. Check filesystem permissions on the Vault root and ensure the SiYuan process can stat it.
  3. If the path involves a network share or removable drive, confirm it is mounted and accessible, then retry analysis.
  4. Inspect the wrapped suffix in the error (after the colon) — it names the exact failing step (empty path, normalize, read root, config read).

Example fix

// before
abs, err := filepath.Abs(filepath.Clean(localPath))
if err != nil {
    return "", fmt.Errorf("%w: normalize Vault path: %v", errObsidianVaultUnreadable, err)
}

// after: caller validates and gives a clear path before calling the API
if strings.TrimSpace(localPath) == "" {
    return fmt.Errorf("Vault path must not be empty")
}
// then call startObsidianVaultAnalysis(localPath)
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate the Vault path is non-empty and resolvable before calling the API
func preflightVaultPath(localPath string) error {
    if strings.TrimSpace(localPath) == "" { return errors.New("Vault path is empty") }
    abs, err := filepath.Abs(filepath.Clean(localPath))
    if err != nil { return fmt.Errorf("cannot resolve path: %w", err) }
    if _, err := os.Lstat(abs); err != nil { return fmt.Errorf("cannot access path: %w", err) }
    return nil
}

Try / catch

task, err := model.StartObsidianVaultAnalysis(localPath)
if err != nil {
    if errors.Is(err, errObsidianVaultUnreadable) {
        // show the wrapped suffix to the user as an actionable filesystem error
        return fmt.Errorf("Vault cannot be read (%s). Check the path exists and is accessible", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling POST /api/import/startObsidianVaultAnalysis with a localPath that is empty, cannot be converted to an absolute path, does not exist on disk (os.Lstat fails), or whose .obsidian config directory exists but cannot be stat'd. validateObsidianVaultRoot wraps this sentinel in each of those branches.

Common situations: Passing an empty string as localPath; a path containing characters invalid for filepath.Abs on the host OS; the Vault directory was deleted or moved between selection and analysis; permissions deny Lstat on the Vault root or its config dir; a network/UNC path that is unreachable.

Related errors


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