siyuan-note/siyuan · error · newObsidianUserError

345

345

Error message

read Markdown [%s]: %w

What it means

This error wraps a failure to read an Obsidian vault Markdown file during import. The kernel opens each analyzed .md file to stage its content; if the file cannot be opened or its content changed since analysis (errObsidianSourceChanged maps to language 346), a user-facing localized error is produced with the file's relative path.

Solutions

  1. Close apps syncing the vault and re-run the import so analysis and staging see a stable snapshot
  2. Verify the file at the reported relative path still exists and is readable (check permissions, open it in an editor)
  3. Re-run the import without modifying the vault while it runs; if it persists, copy the vault to a local folder and import from there

Example fix

// before: importing directly from a synced cloud vault
importObsidian("C:/Users/me/OneDrive/Obsidian/MyVault")
// after: pause sync, or import from a stable local copy
// copy MyVault to D:/tmp/MyVault, then:
importObsidian("D:/tmp/MyVault")
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function assertReadable(absPath) {
  const fd = fs.openSync(absPath, 'r');
  fs.closeSync(fd);
}
// call for each vault .md file before starting the import
files.forEach(f => assertReadable(f.absPath));

Try / catch

try { await apiImportObsidian(vaultPath); }
catch (e) {
  if (/read Markdown \[/.test(e.msg)) {
    console.error('Unreadable vault file:', e.msg); // fix permissions/restore file, retry
  }
}

Prevention

When it happens

Trigger: os.Open fails on file.AbsPath during validateObsidianReadableFile (file deleted, permission denied, path too long), or the file's size/mtime metadata differs from what was recorded during analysis (errObsidianSourceChanged).

Common situations: Vault edited or synced (Syncthing/iCloud/OneDrive) while the import is running; files moved or renamed in the vault between analysis and staging; read-protected files; a locked file on Windows opened by another program.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/a1f0b66ccf97b98e. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/import_obsidian.go:953

	if err != nil {
		return nil, err
	}
	after, err := os.Stat(file.AbsPath)
	if err != nil {
		return nil, err
	}
	if before.Size() != after.Size() || !before.ModTime().Equal(after.ModTime()) {
		return nil, fmt.Errorf("%w while reading", errObsidianSourceChanged)
	}
	return data, nil
}

func newObsidianReadUserError(file *obsidianSourceFile, err error) error {
	language := 345
	if errors.Is(err, errObsidianSourceChanged) {
		language = 346
	}
	return newObsidianUserError(language, file.RelPath, fmt.Errorf("read Markdown [%s]: %w", file.RelPath, err))
}

func validateObsidianReadableFile(file *obsidianSourceFile) error {
	opened, err := os.Open(file.AbsPath)
	if err != nil {
		return err
	}
	defer opened.Close()
	buffer := make([]byte, 1)
	_, err = opened.Read(buffer)
	if err != nil && !errors.Is(err, io.EOF) {
		return err
	}
	return validateObsidianSourceMetadata(file)
}

func buildObsidianHeadingIndex(doc *obsidianDocPlan, tree *parse.Tree) {
	stack := make([]string, 6)

View on GitHub (pinned to 8641553a1f)