siyuan-note/siyuan · error

remove unused asset [ ] failed

Error message

remove unused asset [%s] failed: %w

What it means

The kernel failed to delete an unused asset file from disk during asset cleanup. SiYuan removes assets that are no longer referenced by any document; when the underlying file removal (filelock.RemoveWithoutFatal) fails, the absolute path and OS error are wrapped and pushed to the UI as an error message. The original error is preserved via %w wrapping.

Solutions

  1. Check no other process (editor, sync client, antivirus) is holding the asset file open, then retry the removal
  2. Verify the data directory and workspace/assets folder are writable by the SiYuan process user
  3. Confirm the asset file still exists; if it was already removed by sync or another instance, refresh the asset panel
  4. Check disk health/free space and OS-level error details in the kernel log (logging.LogErrorf output)
  5. Restart SiYuan to release internal handles and retry the cleanup

Example fix

// before: removal fails silently blocking cleanup
if err = filelock.RemoveWithoutFatal(absPath); err != nil { ... }
// after (user-side): close the app holding the asset, ensure file is unlocked, then retry
closeExternalEditors(assetPath); retryAssetRemoval()
Defensive patterns

Strategy: try-catch

Validate before calling

// before triggering cleanup, check writability
import fs from 'fs';
if (!fs.accessSync(assetAbsPath, fs.constants.W_OK)) throw new Error('asset not writable');

Try / catch

try { await removeUnusedAsset(path) } catch (e) {
  if (e.message.includes('remove unused asset')) {
    // file likely locked/permission issue; notify user to close editors/sync clients
    showLockHint(path);
  }
}

Prevention

When it happens

Trigger: RemoveUnusedAsset / HandleAssetsRemoveEvent flow deletes an asset file and the OS remove fails — typically because the file is locked by another process (open in an external editor, AV scanner, sync client), the file was already deleted, or the user lacks write permission on the file or its parent directory.

Common situations: Cloud-sync clients (OneDrive/Dropbox) holding file locks on Windows; antivirus scanning the file mid-delete; read-only data directory; the asset deleted concurrently by another SiYuan instance or sync; filesystem errors on removable/network drives.

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/9f2f16c3c706b792. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/assets.go:1631

	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

	util.RemoveAssetText(relativePath)

	IncSync()

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

func RenameAsset(oldPath, newName string) (newPath string, err error) {
	util.PushEndlessProgress(Conf.Language(110))
	defer util.PushClearProgress()

	oldCleanPath := AssetPathWithoutQuery(oldPath)

View on GitHub (pinned to 9f775e8a12)