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
- Re-read the current file content, recompute the operation on the fresh data, and retry the compare-write
- Retry with backoff if concurrent relink/sync writers are expected to settle
- Serialize the operation so the read-snapshot and the write happen in one step (or under the same higher-level lock)
- 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
- Capture original and write in the tightest possible window
- Retry the read-modify-write loop on conflict rather than failing the whole operation
- Serialize concurrent relink/sync jobs touching the same paths
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
- agent session revision conflict
- attribute view order changed; retry the drag
- OIDC configuration changed during validation
- skill changed; reload it before saving, renaming or deleting
- target already exists or is unreadable
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)