{"record":{"id":"3c65e41123f69c7c","repo":"siyuan-note/siyuan","slug":"copy-unused-asset-s-to-history-failed-w","errorCode":null,"errorMessage":"copy unused asset [%s] to history failed: %w","messagePattern":"copy unused asset \\[(.+?)\\] to history failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1616,"sourceCode":"}\n\nfunc RemoveUnusedAsset(p string) (ret string, err error) {\n\trelativePath, absPath, err := ResolveUnusedDataAssetPath(p)\n\tif err != nil {\n\t\treturn\n\t}\n\tif err = EnsureAssetPrefixLocal(absPath); err != nil {\n\t\treturn\n\t}\n\thistoryDir, err := getHistoryDir(HistoryOpClean)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"get history dir failed: %s\", err)\n\t\treturn\n\t}\n\n\thistoryPath := filepath.Join(historyDir, filepath.FromSlash(relativePath))\n\tif err = filelock.Copy(absPath, historyPath); err != nil {\n\t\terr = fmt.Errorf(\"copy unused asset [%s] to history failed: %w\", absPath, err)\n\t\treturn\n\t}\n\n\thash, _ := util.GetEtag(absPath)\n\tsql.BatchRemoveAssetsQueue([]string{hash})\n\tcache.RemoveAssetHash(hash)\n\n\tif util.IsMobileContainer() {\n\t\tHandleAssetsRemoveEvent(absPath)\n\t}\n\n\tif err = filelock.RemoveWithoutFatal(absPath); err != nil {\n\t\tlogging.LogErrorf(\"remove unused asset [%s] failed: %s\", absPath, err)\n\t\tutil.PushErrMsg(fmt.Sprintf(\"%s\", err), 7000)\n\t\terr = fmt.Errorf(\"remove unused asset [%s] failed: %w\", absPath, err)\n\t\treturn\n\t}\n\tret = absPath","sourceCodeStart":1598,"sourceCodeEnd":1634,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1598-L1634","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif err = filelock.Copy(absPath, historyPath); err != nil {\n    err = fmt.Errorf(\"copy unused asset [%s] to history failed: %w\", absPath, err)\n    return\n}\n// after (caller-side precheck)\nif err := os.MkdirAll(filepath.Dir(historyPath), 0755); err != nil {\n    return fmt.Errorf(\"history dir unavailable, skip cleanup: %w\", err)\n}\nif err = filelock.Copy(absPath, historyPath); err != nil { ... }","handlingStrategy":"try-catch","validationCode":"if err := os.MkdirAll(historyDir, 0755); err != nil {\n    return fmt.Errorf(\"history dir not writable: %w\", err)\n}\nif free, _ := diskFree(historyDir); free < fileSize(absPath) {\n    return errors.New(\"insufficient disk space for history copy\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"copy unused asset\") {\n    // original asset is untouched; fix disk/permissions and retry\n    log.Printf(\"cleanup aborted safely: %v\", err)\n}","preventionTips":["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"],"tags":["go","filelock","history","cleanup"],"backgroundTag":"file-write-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}