{"record":{"id":"0ea8d8012527cfde","repo":"larksuite/cli","slug":"cannot-rename-binary-for-update-w","errorCode":null,"errorMessage":"cannot rename binary for update: %w","messagePattern":"cannot rename binary for update: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/selfupdate/updater_windows.go","lineNumber":32,"sourceCode":"// PrepareSelfReplace renames the running .exe to .old so that npm's\n// postinstall script can write the new binary without hitting EBUSY.\n// Returns a restore function that undoes the rename on failure.\nfunc (u *Updater) PrepareSelfReplace() (restore func(), err error) {\n\tnoop := func() {}\n\n\texe, err := u.resolveExe()\n\tif err != nil {\n\t\treturn noop, nil // best-effort; don't block update\n\t}\n\n\toldPath := exe + \".old\"\n\n\t// Clean up stale .old from a previous upgrade.\n\tvfs.Remove(oldPath)\n\n\t// Rename running.exe → running.exe.old (Windows allows rename of locked files).\n\tif err := vfs.Rename(exe, oldPath); err != nil {\n\t\treturn noop, fmt.Errorf(\"cannot rename binary for update: %w\", err)\n\t}\n\tu.backupCreated = true\n\n\t// Restore: move .old back to the original path.\n\t// Guard with Stat: run.js may have already recovered .old on its own\n\t// during VerifyBinary; if .old is gone, skip to avoid deleting the\n\t// only working binary.\n\t// On any failure, clear backupCreated so CanRestorePreviousVersion\n\t// reports the real outcome instead of claiming success.\n\trestore = func() {\n\t\tif _, err := vfs.Stat(oldPath); err != nil {\n\t\t\tu.backupCreated = false\n\t\t\treturn\n\t\t}\n\t\tvfs.Remove(exe)\n\t\tif err := vfs.Rename(oldPath, exe); err != nil {\n\t\t\tu.backupCreated = false\n\t\t}","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/selfupdate/updater_windows.go#L14-L50","documentation":"On Windows, PrepareSelfReplace renames the running executable to `<exe>.old` so a new binary can take its place, and wraps any vfs.Rename failure as \"cannot rename binary for update\". Although Windows permits renaming a locked running image, other holders or ACL problems can still block the rename, aborting the self-update before replacement.","triggerScenarios":"vfs.Rename(exe, oldPath) fails: another process holds the file with incompatible sharing flags, the .old path exists as a locked file/directory, antivirus locks the executable, or the user lacks modify permission on the install directory.","commonSituations":"Two CLI instances updating concurrently; the binary installed under Program Files or another admin-only directory without elevation; endpoint protection scanning the just-written binary; a leftover locked .old file from a crashed previous update.","solutions":["Close other running instances of the CLI, then retry the update","Remove a stale locked <binary>.old file (possibly after reboot) so the rename target is free","Run the update from a directory the user can write to, or elevate if the binary lives in an admin-only location","Temporarily exclude the CLI's install directory from antivirus scanning"],"exampleFix":"// before (stale backup blocks rename)\nC:\\tools\\lark-cli.exe and C:\\tools\\lark-cli.exe.old both locked -> rename fails\n// after\ndel C:\\tools\\lark-cli.exe.old   (or reboot to release handles), then re-run `lark-cli update`","handlingStrategy":"retry","validationCode":"// Windows: check for a stale/locked backup and writability before updating\nold := exe + \".old\"\nif _, err := os.Stat(old); err == nil {\n    if err := os.Remove(old); err != nil {\n        return fmt.Errorf(\"stale backup %s is locked; close running instances and retry\", old)\n    }\n}\nif f, err := os.OpenFile(filepath.Dir(exe), os.O_WRONLY, 0o666); err != nil {\n    return fmt.Errorf(\"install dir not writable (elevation needed?): %w\", err)\n} else { f.Close() }","typeGuard":null,"tryCatchPattern":"if _, err := PrepareSelfReplace(exe); err != nil {\n    if isWindowsRenameFailure(err) {\n        time.Sleep(retryDelay)\n        return retryPrepareSelfReplace(exe, 3) // transient AV/indexer locks usually clear\n    }\n    return fmt.Errorf(\"update aborted: %w\", err)\n}","preventionTips":["Ensure only one updater instance runs at a time (lock file)","Install user-writable binaries outside admin-only directories","Add antivirus exclusions for the install and temp directories","Clean up stale .old files at startup"],"tags":["selfupdate","windows","rename","file-lock"],"backgroundTag":"file-rename-failed","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}