siyuan-note/siyuan · error

source changed during asset relink

Error message

source changed during asset relink: %s

What it means

WriteFileIfUnchanged implements optimistic concurrency: the caller passes the content it read (original) and the function re-reads the file under a lock, refusing to write if the bytes changed in between. This error means the file on disk no longer matches the snapshot the caller saw, so writing would silently clobber an intermediate modification (typically during asset relink).

Solutions

  1. Re-read the current file content, recompute the operation on the fresh data, and retry the compare-write
  2. Retry with backoff if concurrent relink/sync writers are expected to settle
  3. Serialize the operation so the read-snapshot and the write happen in one step (or under the same higher-level lock)
  4. Investigate what else writes the path; if clobbering is acceptable, write directly without the compare wrapper

Example fix

// before
err := util.WriteFileIfUnchanged(path, staleOriginal, data)
// after
for i := 0; i < 3; i++ {
    current, _ := os.ReadFile(path)
    data = recompute(current)
    err = util.WriteFileIfUnchanged(path, current, data)
    if err == nil { break }
    time.Sleep(time.Millisecond * 100)
}
Defensive patterns

Strategy: retry

Validate before calling

current, err := os.ReadFile(path)
if err != nil { return err }
// use `current` as original right before writing

Try / catch

for attempt := 0; attempt < 3; attempt++ {
    snapshot, _ := os.ReadFile(path)
    newData := recompute(snapshot)
    err := util.WriteFileIfUnchanged(path, snapshot, newData)
    if err == nil { break }
    time.Sleep(100 * time.Millisecond)
}

Prevention

When it happens

Trigger: Calling WriteFileIfUnchanged(path, original, data) where another writer modified path after original was captured; thrown when bytes.Equal(current, original) fails in the relink flow (apply, Save, saveAttributeView, WriteTreeIfUnchanged).

Common situations: Concurrent sync or relink operations touching the same asset/tree; user edits the file while an index/relink job runs; stale snapshot held across a long operation; multiple kernel instances sharing a workspace directory.

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/12fd0c83eef35c21. Report an issue: GitHub.

Appendix: source

Thrown at kernel/util/compare_write.go:28

	"os"

	"github.com/88250/gulu"
	"github.com/siyuan-note/filelock"
)

// WriteFileIfUnchanged 在同一文件锁内比对扫描源并原子写入;original 为 nil 时要求目标不存在。
func WriteFileIfUnchanged(path string, original, data []byte) error {
	filelock.Lock(path)
	defer filelock.Unlock(path)
	current, err := os.ReadFile(path)
	if original == nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("target already exists or is unreadable: %s", path)
		}
	} else if err != nil {
		return err
	} else if !bytes.Equal(current, original) {
		return fmt.Errorf("source changed during asset relink: %s", path)
	}
	return gulu.File.WriteFileSafer(path, data, 0644)
}

View on GitHub (pinned to 9f775e8a12)