siyuan-note/siyuan · error
copy unused asset [%s] to history failed: %w
Error message
copy unused asset [%s] to history failed: %w
What it means
Returned by RemoveUnusedAsset (assets.go:1458) when filelock.Copy fails while copying a single unused asset from its absPath into the HistoryOpClean history directory before deletion. The %w preserves the underlying copy error. The function has already resolved the path via ResolveUnusedDataAssetPath and obtained historyDir via getHistoryDir.
Source
Thrown at kernel/model/assets.go:1458
indexHistoryDir(filepath.Base(historyDir), util.NewLute())
cache.LoadAssets()
return
}
func RemoveUnusedAsset(p string) (ret string, err error) {
relativePath, absPath, err := ResolveUnusedDataAssetPath(p)
if 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 251596fc0d)
Solutions
- Check the wrapped error (err) for the OS cause — ENOSPC (free disk), EACCES/EPERM (permissions), or ENOENT (source vanished); fix the filesystem condition.
- Ensure util.DataDir and the history path are writable and on the same volume as the assets folder so filelock.Copy can rename atomically.
- Re-run RemoveUnusedAsset after the condition clears; the history copy is the precondition for safe deletion, so the asset is left in place on failure.
Example fix
// before: generic abort on first copy failure
if err = filelock.Copy(absPath, historyPath); err != nil {
err = fmt.Errorf("copy unused asset [%s] to history failed: %w", absPath, err)
return
}
// after: tolerate a vanished source (already cleaned) and surface the rest
if err = filelock.Copy(absPath, historyPath); err != nil {
if os.IsNotExist(err) {
return // nothing to do, asset already gone
}
err = fmt.Errorf("copy unused asset [%s] to history failed: %w", absPath, err)
return
} Defensive patterns
Strategy: validation
Validate before calling
// Before calling /api/asset/removeUnusedAsset, confirm the file exists and the dir is writable.
const exists = await fetchPost('/api/file/getFile', { path: assetPath })
if (!exists) { /* already gone; nothing to do */ return } Try / catch
// Treat a vanished source as non-fatal since history copy is the precondition.
try {
await fetchPost('/api/asset/removeUnusedAsset', { path: p })
} catch (e) {
if (!/not exist|no such file/i.test(String(e.message))) throw e
} Prevention
- Ensure the data dir and history path share a volume so filelock.Copy can rename atomically.
- Close external viewers holding the asset before cleanup.
- Monitor free disk space; ENOSPC is the most common copy-failure cause.
When it happens
Trigger: Invoking the single-asset cleanup API (RemoveUnusedAsset, exposed as /api/asset/removeUnusedAsset) when the source asset file is missing or unreadable between listing and copy, when the history directory cannot be written (disk full, permission denied), or when the OS rename/copy across volumes fails inside filelock.Copy.
Common situations: External process deleted or locked the asset after UnusedAssets listed it; read-only or full data directory; cross-device copy where the history dir lives on a different mount; antivirus locking the file on Windows.
Related errors
- remove unused asset [%s] failed: %w
- marketplace package install path already exists
- install local marketplace package failed: %w; rollback faile
- remove community package [%s] failed
- --output is required for docx
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/3c65e41123f69c7c.
Report an issue: GitHub.