{"record":{"id":"3d47982c33727a0d","repo":"router-for-me/CLIProxyAPI","slug":"remove-old-plugin-file-w","errorCode":null,"errorMessage":"remove old plugin file: %w","messagePattern":"remove old plugin file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":561,"sourceCode":"\t}()\n\n\tif errChmod := temp.Chmod(mode); errChmod != nil {\n\t\treturn fmt.Errorf(\"chmod temp plugin file: %w\", errChmod)\n\t}\n\tif _, errWrite := temp.Write(data); errWrite != nil {\n\t\treturn fmt.Errorf(\"write temp plugin file: %w\", errWrite)\n\t}\n\tif errSync := temp.Sync(); errSync != nil {\n\t\treturn fmt.Errorf(\"sync temp plugin file: %w\", errSync)\n\t}\n\tif errClose := temp.Close(); errClose != nil {\n\t\treturn fmt.Errorf(\"close temp plugin file: %w\", errClose)\n\t}\n\tclosed = true\n\tif errRename := os.Rename(tempPath, targetPath); errRename != nil {\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tif errRemove := os.Remove(targetPath); errRemove != nil && !errors.Is(errRemove, os.ErrNotExist) {\n\t\t\t\treturn fmt.Errorf(\"remove old plugin file: %w\", errRemove)\n\t\t\t}\n\t\t\tif errRenameRetry := os.Rename(tempPath, targetPath); errRenameRetry == nil {\n\t\t\t\tremoveTemp = false\n\t\t\t\treturn nil\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"install plugin file: %w\", errRenameRetry)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"install plugin file: %w\", errRename)\n\t}\n\tremoveTemp = false\n\treturn nil\n}\n\nfunc loadedPluginInstallBlocked(options InstallOptions) bool {\n\treturn options.PluginLoaded != nil && strings.EqualFold(options.GOOS, \"windows\") && options.PluginLoaded()\n}\n","sourceCodeStart":543,"sourceCodeEnd":579,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L543-L579","documentation":"Windows-only branch of writeFileAtomic (install.go:558-563). On Windows, os.Rename onto an existing file can fail because the target is open or locked; the installer then tries os.Remove(targetPath) first and renames again. This error wraps that removal failing for a reason other than 'file did not exist' — typically the existing library is loaded into the process (locking the DLL) or held open by another process, including antivirus.","triggerScenarios":"InstallArchive on windows where the old <id>-v<ver>.dll at the target path is memory-mapped by the running server (the plugin is loaded), or locked by a scanner/editor, so both rename and remove fail.","commonSituations":"Upgrading a plugin on a running Windows host without unloading it first; antivirus or indexing services briefly locking freshly-written DLLs.","solutions":["Set InstallOptions.PluginLoaded to a callback reporting the real load state, and unload the plugin (or restart the server) so installs over a loaded DLL are blocked early with the clearer ErrLoadedPluginLocked instead","Retry after stopping the process holding the DLL (or wait out the antivirus lock)","Install a different version — the versioned filename means a new version writes a new path and does not contend with the old file"],"exampleFix":"// before: no callback, install races the loaded DLL\nres, err := client.Install(ctx, plugin, pluginstore.InstallOptions{PluginsDir: dir, GOOS: \"windows\"})\n// after: surface the lock deterministically\nres, err := client.Install(ctx, plugin, pluginstore.InstallOptions{\n    PluginsDir:   dir,\n    GOOS:         \"windows\",\n    PluginLoaded: func() bool { return host.IsPluginLoaded(plugin.ID) },\n})","handlingStrategy":"validation","validationCode":"// Pass PluginLoaded so the store refuses with ErrLoadedPluginLocked EARLY\n// (install.go:296-297) instead of failing mid-rename:\nopts := pluginstore.InstallOptions{\n    PluginsDir:   dir,\n    GOOS:         \"windows\",\n    PluginLoaded: func() bool { return host.IsPluginLoaded(plugin.ID) },\n}\nif pluginstore.InstallBlockedOnWindows(plugin, opts) { // your own pre-check\n    host.ScheduleRestartThenInstall(plugin)\n}","typeGuard":null,"tryCatchPattern":"if _, err := client.Install(ctx, plugin, opts); err != nil {\n    if errors.Is(err, pluginstore.ErrLoadedPluginLocked) {\n        // planned case: unload/restart, then re-run install\n        return\n    }\n    if runtime.GOOS == \"windows\" && strings.Contains(err.Error(), \"remove old plugin file\") {\n        // target locked by another process (AV?): wait and retry once\n    }\n}","preventionTips":["Always set InstallOptions.PluginLoaded on Windows hosts","Unload plugins or restart the server before upgrading a loaded one","Install new versions rather than overwriting old ones — the versioned filename avoids lock contention entirely"],"tags":["pluginstore","windows","file-locking","install"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}