siyuan-note/siyuan · error
target already exists or is unreadable
Error message
target already exists or is unreadable: %s
What it means
WriteFileIfUnchanged writes data to path atomically under a file lock. When original is nil the function's contract is create-if-absent: it requires the target not to exist. This error is thrown when the target file already exists on disk (or exists but cannot be read, e.g. a directory or permission problem), so an unconditional overwrite would be unsafe.
Solutions
- Check os.Stat(path) before calling and skip or load the existing file when it exists
- If overwriting is intended, read the current content and pass it as original instead of nil
- If the file was just created by the same flow, read it and compare/write with the actual original bytes
- Handle the returned error by re-reading the target and merging/retrying idempotently
Example fix
// before
err := util.WriteFileIfUnchanged(path, nil, data)
// after
if _, statErr := os.Stat(path); statErr == nil {
existing, _ := os.ReadFile(path)
err = util.WriteFileIfUnchanged(path, existing, data)
} else {
err = util.WriteFileIfUnchanged(path, nil, data)
} Defensive patterns
Strategy: validation
Validate before calling
func canCreate(path string) error {
if _, err := os.Stat(path); err == nil {
return fmt.Errorf("target exists: %s", path)
} else if !os.IsNotExist(err) {
return err
}
return nil
} Try / catch
err := util.WriteFileIfUnchanged(path, nil, data)
if err != nil && strings.Contains(err.Error(), "target already exists") {
// load existing content and merge or skip
} Prevention
- Always stat the target before a create-only write
- Pass a real snapshot as original whenever one exists instead of nil
- Treat create-only writes as idempotent: on this error, read the existing file and decide
When it happens
Trigger: Calling WriteFileIfUnchanged(path, nil, data) while path already exists; callers include saveAttributeView, WriteTreeIfUnchanged, apply, and Save when creating a brand-new file without a source snapshot.
Common situations: A create-only write racing with another process/component that created the file first (e.g. duplicate asset relink, attribute view save after the .av JSON was already written); passing nil by mistake when a source snapshot was available; the target path exists as a directory so ReadFile fails with EISDIR.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- source changed during asset relink
- 239
- 345
- access to sensitive workspace file is forbidden
- accessing assets in encrypted notebook
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/dcd2bb4ba8530337.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/compare_write.go:23
package util
import (
"bytes"
"fmt"
"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)