{"record":{"id":"55ab966b39367b15","repo":"multica-ai/multica","slug":"move-running-binary-aside-w","errorCode":null,"errorMessage":"move running binary aside: %w","messagePattern":"move running binary aside: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/cli/update_windows.go","lineNumber":36,"sourceCode":"// replaceBinary swaps the running executable for the freshly-downloaded one.\n// Windows holds an exclusive handle on a running .exe, so the rename-over\n// pattern used on Unix fails with \"Access is denied\". Instead:\n//  1. Clear any stale leftover from a previous update.\n//  2. Move the running executable aside to exePath+\".old\".\n//  3. Rename the new binary into place.\n//  4. If step 3 fails, restore the original so the user isn't stranded.\n//\n// The leftover .old file is cleaned up on next startup via\n// CleanupStaleUpdateArtifacts.\nfunc replaceBinary(tmpPath, exePath string) error {\n\toldPath := exePath + oldBinarySuffix\n\n\t// Best-effort cleanup; if this fails (file still locked) the next Rename\n\t// will surface a useful error.\n\t_ = os.Remove(oldPath)\n\n\tif err := os.Rename(exePath, oldPath); err != nil {\n\t\treturn fmt.Errorf(\"move running binary aside: %w\", err)\n\t}\n\n\tif err := os.Rename(tmpPath, exePath); err != nil {\n\t\t// Restore so the user isn't left without a multica.exe.\n\t\tif rerr := os.Rename(oldPath, exePath); rerr != nil {\n\t\t\treturn fmt.Errorf(\"install new binary: %w (and failed to restore: %v)\", err, rerr)\n\t\t}\n\t\treturn fmt.Errorf(\"install new binary: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// CleanupStaleUpdateArtifacts removes leftover `.old` binaries from previous\n// updates. Windows can't delete a running .exe, so a prior update may have\n// left one behind; once the user restarts, this call reclaims the space.\nfunc CleanupStaleUpdateArtifacts() {\n\texePath, err := selfexec.Resolve()","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/cli/update_windows.go#L18-L54","documentation":"Windows-only replaceBinary failure: os.Rename(exePath, exePath+\".old\") could not move the currently running executable aside. Windows locks a running .exe against delete/rename-in-place, so this fires when something holds an unexpected handle on the binary, the directory denies write access, or the previously removed .old path is still locked by another process.","triggerScenarios":"Running the self-update while a second multica instance (or a crashed child process) still holds the exe open, antivirus scanning the binary at the moment of rename, insufficient NTFS permissions on the install directory, or the exe living on a read-only/network mount.","commonSituations":"Two daemons started from the same install path, AV/EDR agents briefly locking freshly downloaded binaries, running from a mounted share, or install dirs under Program Files without elevation.","solutions":["Stop all multica processes (taskmgr / Get-Process multica) and retry the update","Add an AV exclusion for the install directory, or retry after a few seconds so the AV scan handle is released","Move the install to a user-writable directory (or run the update elevated if under Program Files)","If a stale .old file is the problem, reboot (releases the lock) so CleanupStaleUpdateArtifacts can remove it, then update"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before replacing, check for other running instances of this exe.\nfunc otherInstancesRunning() bool {\n    self, _ := os.Executable()\n    procs, _ := gops.Processes() // or enumerate via tasklist on Windows\n    for _, p := range procs {\n        if exe, err := p.Exe(); err == nil && strings.EqualFold(exe, self) && p.Pid() != os.Getpid() {\n            return true\n        }\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":"Catch sharing-violation errors (syscall.ERROR_SHARING_VIOLATION via errors.As on *os.LinkError/*os.PathError) and retry the rename 2-3 times with a short backoff before giving up.","preventionTips":["Run exactly one daemon per install path","Add AV exclusions for the multica install directory","Install in a user-writable directory, not Program Files, unless elevated"],"tags":["windows","self-update","file-lock","rename"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}