siyuan-note/siyuan · error

copy unused asset [ ] to history failed

Error message

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

What it means

When cleaning up unused assets, before deleting a file the code copies it into the history directory via filelock.Copy. If the copy fails, the error is wrapped as "copy unused asset [%s] to history failed: %w" and the cleanup aborts for that asset, leaving the original untouched. This guarantees unused assets are always recoverable from history before removal.

Solutions

  1. Check disk space and write permissions on the workspace history directory
  2. Ensure the history directory exists (it is created by GetHistoryDir; verify no external tool removed it)
  3. Close tools locking the asset file (backup/AV scanners) and retry the cleanup
  4. Avoid running multiple cleanup operations concurrently; retry the failed asset individually

Example fix

// before
if err = filelock.Copy(absPath, historyPath); err != nil {
    err = fmt.Errorf("copy unused asset [%s] to history failed: %w", absPath, err)
    return
}
// after (caller-side precheck)
if err := os.MkdirAll(filepath.Dir(historyPath), 0755); err != nil {
    return fmt.Errorf("history dir unavailable, skip cleanup: %w", err)
}
if err = filelock.Copy(absPath, historyPath); err != nil { ... }
Defensive patterns

Strategy: try-catch

Validate before calling

if err := os.MkdirAll(historyDir, 0755); err != nil {
    return fmt.Errorf("history dir not writable: %w", err)
}
if free, _ := diskFree(historyDir); free < fileSize(absPath) {
    return errors.New("insufficient disk space for history copy")
}

Try / catch

if err != nil && strings.Contains(err.Error(), "copy unused asset") {
    // original asset is untouched; fix disk/permissions and retry
    log.Printf("cleanup aborted safely: %v", err)
}

Prevention

When it happens

Trigger: Running unused-asset cleanup where filelock.Copy(absPath, historyPath) fails — e.g. the history directory is missing or unwritable, the source asset vanished mid-copy, disk is full, or the file lock on the source/target cannot be acquired.

Common situations: Read-only or full disks; history directory deleted or restored without correct permissions; antivirus/backup software holding locks on asset files on Windows; concurrent cleanup tasks competing for the same file lock.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/3c65e41123f69c7c. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/assets.go:1616

}

func RemoveUnusedAsset(p string) (ret string, err error) {
	relativePath, absPath, err := ResolveUnusedDataAssetPath(p)
	if err != nil {
		return
	}
	if err = EnsureAssetPrefixLocal(absPath); err != nil {
		return
	}
	historyDir, err := getHistoryDir(HistoryOpClean)
	if err != nil {
		logging.LogErrorf("get history dir failed: %s", err)
		return
	}

	historyPath := filepath.Join(historyDir, filepath.FromSlash(relativePath))
	if err = filelock.Copy(absPath, historyPath); err != nil {
		err = fmt.Errorf("copy unused asset [%s] to history failed: %w", absPath, err)
		return
	}

	hash, _ := util.GetEtag(absPath)
	sql.BatchRemoveAssetsQueue([]string{hash})
	cache.RemoveAssetHash(hash)

	if util.IsMobileContainer() {
		HandleAssetsRemoveEvent(absPath)
	}

	if err = filelock.RemoveWithoutFatal(absPath); err != nil {
		logging.LogErrorf("remove unused asset [%s] failed: %s", absPath, err)
		util.PushErrMsg(fmt.Sprintf("%s", err), 7000)
		err = fmt.Errorf("remove unused asset [%s] failed: %w", absPath, err)
		return
	}
	ret = absPath

View on GitHub (pinned to 9f775e8a12)