{"record":{"id":"cc545c42c95f97c6","repo":"router-for-me/CLIProxyAPI","slug":"remove-stale-shadow-plugin-w","errorCode":null,"errorMessage":"remove stale shadow plugin: %w","messagePattern":"remove stale shadow plugin: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/loader_windows.go","lineNumber":170,"sourceCode":"\tsize, errCopy := io.Copy(io.MultiWriter(tmp, hasher), in)\n\tif errCopy != nil {\n\t\t_ = tmp.Close()\n\t\treturn \"\", errCopy\n\t}\n\tif errClose := tmp.Close(); errClose != nil {\n\t\treturn \"\", errClose\n\t}\n\tdigest := hex.EncodeToString(hasher.Sum(nil))\n\ttarget := shadowPluginPath(dir, file.ID, digest, filepath.Ext(source))\n\tif shadowPluginMatches(target, size, digest) {\n\t\treturn target, nil\n\t}\n\tif errRemove := os.Remove(target); errRemove != nil && !errors.Is(errRemove, os.ErrNotExist) {\n\t\tif shadowPluginMatches(target, size, digest) {\n\t\t\treturn target, nil\n\t\t}\n\t\tremoveShadowPlugin(target)\n\t\treturn \"\", fmt.Errorf(\"remove stale shadow plugin: %w\", errRemove)\n\t}\n\tif errRename := os.Rename(tmpName, target); errRename != nil {\n\t\tif shadowPluginMatches(target, size, digest) {\n\t\t\treturn target, nil\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"move shadow plugin: %w\", errRename)\n\t}\n\tremoveTemp = false\n\treturn target, nil\n}\n\nfunc shadowPluginDir() (string, error) {\n\tdir := filepath.Join(os.TempDir(), \"cliproxy-pluginhost\", shadowPluginProcessDirName(os.Getpid()))\n\tif errMkdir := os.MkdirAll(dir, 0o700); errMkdir != nil {\n\t\treturn \"\", errMkdir\n\t}\n\treturn dir, nil\n}","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/loader_windows.go#L152-L188","documentation":"Before loading a Windows plugin, the host shadow-copies it into %TEMP%\\cliproxy-pluginhost\\<pid>. If an existing shadow file with the expected digest path is stale (digest mismatch) and os.Remove fails (and the file on disk does not verify as a valid copy), this error surfaces the underlying remove failure. On Windows this almost always means the file is locked by a running process.","triggerScenarios":"A previous instance of the server (or an antivirus scanner, indexer, or debugger) still holds the shadow DLL open; two server processes sharing the same PID directory after a PID reuse; the shadow file has read-only attributes.","commonSituations":"Old server process not fully exited when a new one starts; antivirus quarantining or locking freshly written DLLs; crash leaving a zombie process holding the handle.","solutions":["Stop all running instances of the server (check Task Manager for lingering processes) and retry","Delete the stale shadow directory %TEMP%\\cliproxy-pluginhost manually once no process holds it","Add an antivirus exclusion for the cliproxy-pluginhost temp directory or the plugin artifact","Remove read-only attributes from the temp directory contents"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var target string\nerr := retry(3, time.Second, func() error {\n    var e error\n    target, e = shadowCopyPlugin(file)\n    return e\n}) // file locks are often transient","preventionTips":["Ensure only one server instance runs per shadow dir; stop old processes before starting new ones","Exclude %TEMP%\\cliproxy-pluginhost from antivirus real-time scanning","Clean the shadow dir in deployment scripts while the server is stopped"],"tags":["plugin","windows","file-lock","shadow-copy","dynamic-library"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}