{"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/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-installer/src/install_macos.rs#L222-L258","documentation":"After the app swap, recreating the 'but' symlink failed; rollback then tried to remove the new app directory to put the backup back, and that removal also failed. The message reports both errors plus the backup path. Likely end state: new app installed but no working but symlink, and the previous app still sitting at the printed backup path.","triggerScenarios":"unix_fs::symlink fails (the but link path is occupied by a non-removable object or its directory is not writable) while simultaneously remove_dir_all on the new app dir fails (bundle files locked by a running app, SIP/MDM protection, permissions).","commonSituations":"App launched between swap and symlink; permissions changed on ~/.local/bin or the install dir since the last update; MDM policy blocking both operations.","solutions":["Manually finish the rollback: rm -rf the new app dir, mv '<backup path from the message>' into the install location, then ln -s <app>/Contents/MacOS/gitbutler-tauri ~/.local/bin/but","If the new app actually runs, keep it and just recreate the symlink by hand; delete the backup","Fix permissions on both the symlink dir and the app dir, then rerun the installer"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Ensure the symlink directory is writable before install\nlet bin_dir = home.join(\".local/bin\");\nstd::fs::create_dir_all(&bin_dir)?;\nlet probe = bin_dir.join(\".write-probe\");\nstd::fs::write(&probe, b\"\")?;\nstd::fs::remove_file(&probe)?;","typeGuard":null,"tryCatchPattern":"if let Err(e) = but_installer::run_installation_with_version(request, false) {\n    let msg = e.to_string();\n    if msg.contains(\"failed to remove new installation during rollback\") {\n        // manual recovery using the printed \"Backup at:\" path\n        let backup = msg.split(\"Backup at: \").nth(1).unwrap_or_default().trim();\n        manual_restore(std::path::Path::new(backup))?;\n    }\n    return Err(e);\n}","preventionTips":["Verify write access to the but symlink directory before updating","Quit the app before updates so bundle files are not locked","Note the backup path pattern during updates for manual recovery"],"tags":["rust","macos","symlink","rollback-failed","but-installer"],"backgroundTag":"installation-rollback","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}