{"record":{"id":"36dc286f24f5bb57","repo":"gitbutlerapp/gitbutler","slug":"failed-to-create-symlink-and-failed-to-remove-new","errorCode":null,"errorMessage":"Failed to create symlink and failed to remove new installation during rollback: {}. Backup at: {}","messagePattern":"Failed to create symlink and failed to remove new installation during rollback: (.+?)\\. Backup at: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/but-installer/src/install_macos.rs","lineNumber":240,"sourceCode":"        bail!(\"Failed to move new installation into place: {e}\");\n    }\n\n    // Update the symlink to point to the new installation\n    let final_target = install_app.join(\"Contents/MacOS/gitbutler-tauri\");\n    let _ = fs::remove_file(&but_symlink);\n\n    // Try to create symlink and verify - if either fails, rollback\n    let symlink_result = unix_fs::symlink(&final_target, &but_symlink);\n    let _ = fs::remove_file(&but_new);\n\n    if let Err(e) = symlink_result {\n        // Symlink creation failed - rollback to backup\n        warn(&format!(\n            \"Failed to create symlink: {e} - attempting to restore backup\"\n        ));\n        if install_app_backup.exists() {\n            if let Err(remove_err) = fs::remove_dir_all(&install_app) {\n                bail!(\n                    \"Failed to create symlink and failed to remove new installation during rollback: {}. Backup at: {}\",\n                    remove_err,\n                    install_app_backup.display()\n                );\n            }\n            fs::rename(&install_app_backup, &install_app)?;\n\n            let restored_target = install_app.join(\"Contents/MacOS/gitbutler-tauri\");\n            let _ = fs::remove_file(&but_symlink);\n            let _ = unix_fs::symlink(&restored_target, &but_symlink);\n\n            bail!(\"Failed to create symlink: {e}. Previous installation was restored.\");\n        } else {\n            bail!(\"Failed to create symlink: {e}. No backup available to restore.\");\n        }\n    }\n\n    if !validate_installed_binary(&but_symlink) {","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-installer/src/install_macos.rs#L222-L258","documentation":"After the new .app was moved into place, creating the `but` symlink failed. Rollback tried to remove the newly installed app directory (fs::remove_dir_all(&install_app)) so the backup could be renamed back, and that removal itself failed. The system is left in a dual-state: new (possibly symlink-less) install plus a backup, so the error message reports the backup path.","triggerScenarios":"install_app when unix_fs::symlink(final_target, but_symlink) fails AND install_app_backup exists AND fs::remove_dir_all(&install_app) fails during rollback - typically because files in the new app bundle are in use or permission-protected.","commonSituations":"A GitButler process was launched from the new app between the rename and the symlink step; permission-controlled directories inside the new bundle (added malware protections, AV scan); the `but` path in ~/.local/bin is itself a directory so symlink(2) fails with EEXIST while rm -rf of the app also hits a busy file.","solutions":["Quit any GitButler processes (`pkill -f gitbutler-tauri`) and retry the install","Manually complete the rollback: `rm -rf <new GitButler.app>` then `mv <backup path> <install path>` using the backup path in the message","Check ~/.local/bin/but is not a directory (`file ~/.local/bin/but`); remove it so a symlink can replace it","Investigate why remove_dir_all failed (immutable flags, permissions, AV lock) with `ls -laO` on the app bundle"],"exampleFix":"// before: manual recovery guesswork\n// after: scripted recovery from the message\nrm -rf ~/Applications/GitButler.app\nmv ~/.local/share/gitbutler/GitButler.app.backup ~/Applications/GitButler.app","handlingStrategy":"try-catch","validationCode":"// Pre-checks: no processes using the app, symlink path clear\nlet but_path = home.join(\".local/bin/but\");\nif let Ok(m) = fs::symlink_metadata(&but_path) {\n    if !m.file_type().is_symlink() {\n        bail!(\"{} is not a symlink - symlink creation will fail\", but_path.display());\n    }\n}\nlet _ = Command::new(\"pgrep\").args([\"-f\", \"gitbutler-tauri\"]).output()?;","typeGuard":null,"tryCatchPattern":"if let Err(e) = installer::download_and_install_app(...) {\n    if e.to_string().contains(\"failed to remove new installation during rollback\") {\n        // dual state: finish rollback manually using the backup path in the message\n        eprintln!(\"manually restore from the backup path shown in: {e}\");\n    }\n}","preventionTips":["Never launch the app during an in-progress install","Keep ~/.local/bin/but as a symlink managed only by the installer","Disable AV real-time scanning of the install directory during updates","If you see this error, finish the rollback manually before retrying to avoid a divergent state"],"tags":["filesystem","symlink","rollback-failed","installation"],"backgroundTag":"file-write-failed","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}