siyuan-note/siyuan · error

source changed during scan

Error message

source changed during scan: %s

What it means

To support undo/history, apply() re-reads each source file it scanned and compares it byte-for-byte with the snapshot taken during scanning. If the file cannot be read or its content differs, apply aborts with this error before writing any rewrite, ensuring no relink is applied over silently changed content.

Solutions

  1. Retry the whole relink request after the modifications settle so a fresh scan snapshot is taken
  2. Ensure no editor clients or sync jobs write to the workspace during the operation
  3. Serialize relink requests (don't run find and relink concurrently on the same paths)
  4. Check file permissions/locks if readErr (not content change) is the recurring cause

Example fix

// before
errCh := make(chan error, 2)
go func() { _, err := model.FindAssetReferencesBatch(ctx, paths); errCh <- err }()
go func() { _, err := model.RelinkAssets(ctx, mappings, false); errCh <- err }() // races with scan
// after
_, err := model.FindAssetReferencesBatch(ctx, paths)
_, err = model.RelinkAssets(ctx, mappings, false) // sequential; retry on 'source changed'
Defensive patterns

Strategy: retry

Try / catch

_, err := model.RelinkAssets(ctx, mappings, dryRun)
if err != nil && strings.Contains(fmt.Sprint(err), "source changed during scan") {
	time.Sleep(backoff)
	_, err = model.RelinkAssets(ctx, mappings, dryRun) // rescan and reapply
}

Prevention

When it happens

Trigger: A document file (.sy) covered by the plan is edited/saved between scan and apply; the file becomes unreadable (permissions, lock); external tools rewrite the file during the scan window.

Common situations: User typing in the editor while a relink batch runs; sync applying remote changes mid-scan; two concurrent relink/find operations overlapping on the same documents.

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/97d3324cae8d4f60. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/asset_relink_batch.go:457

	if err = p.checkContext(); err != nil {
		return err
	}
	historyDir, err := newAssetRelinkHistoryDir()
	if err != nil {
		return err
	}
	p.result.HistoryPath = filepath.ToSlash(historyDir)
	// 所有候选源先备份;备份失败属于请求级错误,此时尚未修改任何引用。
	for _, file := range files {
		if err = p.checkContext(); err != nil {
			return err
		}
		if file.before == nil {
			continue
		}
		current, readErr := filelock.ReadFile(file.path)
		if readErr != nil || !bytes.Equal(current, file.before) {
			return fmt.Errorf("source changed during scan: %s", file.path)
		}
		rel, relErr := filepath.Rel(util.DataDir, file.path)
		if relErr != nil || strings.HasPrefix(rel, "..") {
			return errors.New("invalid history source path")
		}
		dest := filepath.Join(historyDir, rel)
		if err = os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
			return err
		}
		if err = gulu.File.WriteFileSafer(dest, file.before, 0644); err != nil {
			return err
		}
	}
	indexHistoryDir(filepath.Base(historyDir), util.NewLute())
	changedViews, reload := map[string]bool{}, map[string]bool{}
	defer func() {
		for _, tree := range p.trees {
			viewChanged := false

View on GitHub (pinned to 9f775e8a12)