siyuan-note/siyuan · warning

Obsidian source file changed

Error message

Obsidian source file changed

What it means

`errObsidianSourceChanged` signals TOCTOU protection: before analysis or after reading a Vault note, the file's size and mtime are re-stat'ed and compared to the pre-read snapshot; any difference aborts the read of that file so the importer never parses a half-written or concurrently modified file. It is intentionally opaque to users (see `TestObsidianUserErrorDataDoesNotExposeCause`).

Solutions

  1. Close Obsidian and pause sync clients, then retry the import
  2. Re-run the import — the check is per-attempt and succeeds when the file is stable
  3. Copy the Vault to a static local folder and import from the copy

Example fix

// before
// import while Obsidian autosaves the note -> stat mismatch
obsidianReadFile(file) // wrapped: errObsidianSourceChanged during analysis
// after
// close Obsidian / pause sync, or import from a static copy
obsidianReadFile(file) // stable stat, read succeeds
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    await api.importObsidianVault(vaultPath);
    break;
  } catch (e) {
    if (!isSourceChangedError(e)) throw e;
    await sleep(1000); // let writer settle, then retry
  }
}

Prevention

When it happens

Trigger: `obsidianReadStable` path: `before.Size() != file.Size || !before.ModTime().Equal(file.ModTime)` after analysis, or the after-read stat differs — i.e. the `.md` file changed size or mtime between stat, analysis, and `os.ReadFile`.

Common situations: Obsidian (or a sync client like Obsidian Sync/iCloud/Dropbox) is open and autosaving while the import runs; an editor/sorter touched the note mid-import; filesystem timestamps were rewritten by a sync scan.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/08aa1ae8416434e0. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/import_obsidian.go:235

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()
				return nil, errors.New(Conf.Language(329))
			}
			if active.Cancel != nil {
				active.Cancel()

View on GitHub (pinned to 9f775e8a12)