siyuan-note/siyuan · error

path, size or modification time differs

Error message

path, size or modification time differs

What it means

validateObsidianSourceMetadata stats the source file on disk and compares its size and modification time with the metadata recorded during the vault analysis phase. Any difference means the file changed after analysis, so the import treats it as untrustworthy and fails; it is the sentinel message wrapped by error 346.

Source

Thrown at kernel/model/import_obsidian.go:1866

	for _, asset := range vault.ImportAssets {
		if err := ctx.Err(); err != nil {
			return err
		}
		if err := validateObsidianSourceMetadata(asset.Source); err != nil {
			return newObsidianUserError(346, asset.Source.RelPath,
				fmt.Errorf("attachment [%s] changed after analysis: %w", asset.Source.RelPath, err))
		}
	}
	return nil
}

func validateObsidianSourceMetadata(file *obsidianSourceFile) error {
	info, err := os.Stat(file.AbsPath)
	if err != nil {
		return err
	}
	if info.Size() != file.Size || !info.ModTime().Equal(file.ModTime) {
		return errors.New("path, size or modification time differs")
	}
	return nil
}

func transformObsidianMarkdown(vault *obsidianVaultContext, doc *obsidianDocPlan, data []byte) ([]byte, *obsidianTransformStats) {
	scan := scanObsidianSource(data)
	stats := &obsidianTransformStats{ReservedIDs: map[string]bool{}, FootnoteIDs: map[string]bool{}, PreservedComments: len(scan.Comments)}
	for _, heading := range doc.Headings {
		stats.ReservedIDs[heading.ID] = true
	}
	for _, id := range doc.BlockIDs {
		stats.ReservedIDs[id] = true
	}

	type replacement struct {
		start int
		end   int
		text  string

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Ensure no process writes to the Obsidian vault between starting the import and its completion
  2. Re-run the import; the fresh analysis will record current size/mtime and validation will pass
  3. Import from a read-only or frozen copy of the vault
  4. Check for sync/backup tools resetting mtimes on vault files

Example fix

// before: file modified after analysis -> info.Size() != file.Size
if info.Size() != file.Size || !info.ModTime().Equal(file.ModTime) {
	return errors.New("path, size or modification time differs")
}
// after (caller-side): re-run analysis so recorded metadata matches disk
vault := analyzeVault(vaultPath) // fresh Stat metadata
err := importVault(vault) // validation now passes
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(absPath)
if err != nil { return err }
if info.Size() != recorded.Size || !info.ModTime().Equal(recorded.ModTime) {
	return fmt.Errorf("%s changed since analysis", recorded.RelPath)
}

Try / catch

if err := importObsidianVault(p, nb); err != nil && strings.Contains(err.Error(), "modification time differs") {
	vault = analyzeVault(p); err = importVault(vault) // re-analyze and retry
}

Prevention

When it happens

Trigger: os.Stat succeeds on file.AbsPath but info.Size() != file.Size or info.ModTime() is not exactly equal to the file.ModTime recorded at analysis time.

Common situations: Attachment saved/rewritten after import started; tools that rewrite a file (formatters, editors, sync clients) even if content is identical but size/mtime changed; filesystems with coarse or shifting timestamp precision; files replaced by a newer version during the import window.

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@8641553a1f (2026-09-11). Data as JSON: /api/errors/8ac0d980571ab3bc. Report an issue: GitHub.