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

  1. Check os.Stat(path) before calling and skip or load the existing file when it exists
  2. If overwriting is intended, read the current content and pass it as original instead of nil
  3. If the file was just created by the same flow, read it and compare/write with the actual original bytes
  4. 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

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


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)