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
- Check disk space and write permissions on the workspace history directory
- Ensure the history directory exists (it is created by GetHistoryDir; verify no external tool removed it)
- Close tools locking the asset file (backup/AV scanners) and retry the cleanup
- 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
- Ensure adequate free disk space before running cleanup
- Exclude workspace history/assets dirs from AV and backup scanners on Windows
- Do not run multiple cleanup passes concurrently
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
- Conf.Language(323)
- encrypted attribute view snapshot is missing notebook…
- invalid historical notebook encryption key
- invalid history source path
- remove empty plugin storage directory
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 = absPathView on GitHub (pinned to 9f775e8a12)