{"record":{"id":"87d00ed841a82f74","repo":"router-for-me/CLIProxyAPI","slug":"move-shadow-plugin-w","errorCode":null,"errorMessage":"move shadow plugin: %w","messagePattern":"move shadow plugin: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/loader_windows.go","lineNumber":176,"sourceCode":"\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}\n\nfunc shadowPluginProcessDirName(pid int) string {\n\treturn fmt.Sprintf(\"%s%d\", shadowPluginProcessDirPrefix, pid)\n}\n\nfunc removeShadowPlugin(path string) {","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/loader_windows.go#L158-L194","documentation":"During Windows plugin shadow-copy, after (re)moving a stale target, os.Rename of the freshly written temp file to the target path failed and the existing target does not verify as a valid copy. Rename on Windows fails when the destination exists and is open in another process, or when source and target are locked.","triggerScenarios":"The shadow DLL is currently mapped by this or another process (Windows does not allow overwriting a loaded DLL — which is why shadow-copy exists, but the same-name target can still be locked by a different PID reuse); antivirus holding the new temp file; disk-full on the temp volume.","commonSituations":"Rapid plugin reload churn while requests are in flight; two server processes racing in the same shadow dir; low disk space in %TEMP%.","solutions":["Terminate duplicate/lingering server processes so only one owns the shadow directory","Free space in %TEMP% and clear the cliproxy-pluginhost directory when the server is stopped","Retry the load after a short delay (locks from scanners are usually transient)","Exclude the plugin temp dir from real-time antivirus scanning"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := retryWithBackoff(3, func() error {\n    _, e := shadowCopyPlugin(file)\n    return e\n})","preventionTips":["Avoid rapid plugin reload churn while requests are in flight","Keep %TEMP% volume from filling (shadow copies need space)","Kill orphaned processes before deploys"],"tags":["plugin","windows","file-lock","rename","shadow-copy"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}