siyuan-note/siyuan · error

copy asset [%s] to [%s] failed: %w

Error message

copy asset [%s] to [%s] failed: %w

What it means

createAssetsHistory (kernel/model/history.go:903-908) fails when filelock.Copy of the asset into the history tree errors with anything other than os.IsNotExist (missing sources are deliberately skipped with continue). The %w preserves the real I/O error: permission denied on source or destination, disk full mid-copy, or a file held with an exclusive lock by another process (typical on Windows).

Source

Thrown at kernel/model/history.go:907

	if err != nil {
		return fmt.Errorf("get history directory failed: %w", err)
	}

	for _, file := range assets {
		assetRelPath, relErr := filepath.Rel(filepath.Join(util.DataDir, "assets"), file)
		if relErr != nil || assetRelPath == "." || strings.HasPrefix(assetRelPath, ".."+string(filepath.Separator)) {
			return errors.New("asset path must be under assets")
		}
		historyPath := filepath.Join(historyDir, "assets", assetRelPath)
		if err = os.MkdirAll(filepath.Dir(historyPath), 0755); err != nil {
			return fmt.Errorf("create history directory [%s] failed: %w", filepath.Dir(historyPath), err)
		}

		if err = filelock.Copy(file, historyPath); err != nil {
			if os.IsNotExist(err) {
				continue
			}
			return fmt.Errorf("copy asset [%s] to [%s] failed: %w", file, historyPath, err)
		}
	}

	indexHistoryDir(filepath.Base(historyDir), util.NewLute())
	return
}

func (box *Box) generateDocHistory0() {
	files := box.recentModifiedDocs()
	if 1 > len(files) {
		return
	}

	historyDir, err := getHistoryDir(HistoryOpUpdate)
	if err != nil {
		logging.LogErrorf("get history dir failed: %s", err)
		return
	}

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Read the wrapped error (errors.Is fs.ErrPermission / fs.ErrNoSpace etc.) and fix that specific condition
  2. Verify read permission on the source asset and write permission on the history destination
  3. Free disk space and retry; snapshot generation runs periodically and will succeed once space exists
  4. On Windows, close or exclude the workspace from programs holding exclusive locks, then retry
Defensive patterns

Strategy: retry

Validate before calling

if fi, err := os.Stat(file); err != nil {
    return err
} else if fi.Mode().Perm()&0400 == 0 {
    return fmt.Errorf("asset not readable: %s", file)
}

Try / catch

var err error
for i := 0; i < 3; i++ {
    if err = createAssetsHistory(files); err == nil {
        break
    }
    if !errors.Is(err, fs.ErrPermission) && !errors.Is(err, fs.ErrTemporary) {
        break // permanent error, inspect wrapped cause
    }
    time.Sleep(time.Duration(i+1) * time.Second)
}

Prevention

When it happens

Trigger: The source asset is unreadable (mode 000, ownership mismatch), the destination history dir became read-only, ENOSPC hits mid-copy of a large media file, or an antivirus/indexer/other program holds the asset open exclusively on Windows.

Common situations: AV scanners or sync clients locking asset files on Windows; Docker volume permission drift; disks filling during snapshots of large video/audio assets.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/35c88afeb0ccd7c7. Report an issue: GitHub.