{"record":{"id":"24183f3e85248471","repo":"gitbutlerapp/gitbutler","slug":"osascript-directory-picker-failed-exit","errorCode":null,"errorMessage":"osascript directory picker failed (exit {:?}): {}","messagePattern":"osascript directory picker failed \\(exit (.+?)\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-server/src/lib.rs","lineNumber":129,"sourceCode":"\n/// Shell out to a platform-native directory picker.\nfn native_pick_directory() -> anyhow::Result<Option<String>> {\n    #[cfg(target_os = \"macos\")]\n    {\n        let output = std::process::Command::new(\"osascript\")\n            .arg(\"-e\")\n            .arg(\n                r#\"set theFolder to choose folder with prompt \"Select a Git repository\"\nreturn POSIX path of theFolder\"#,\n            )\n            .output()?;\n        if !output.status.success() {\n            let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();\n            // osascript exits with code 1 and \"User canceled\" on cancel\n            if stderr.contains(\"User canceled\") || stderr.contains(\"(-128)\") {\n                return Ok(None);\n            }\n            anyhow::bail!(\n                \"osascript directory picker failed (exit {:?}): {}\",\n                output.status.code(),\n                if stderr.is_empty() {\n                    \"unknown error\"\n                } else {\n                    &stderr\n                }\n            );\n        }\n        let path = String::from_utf8_lossy(&output.stdout).trim().to_string();\n        if path.is_empty() {\n            return Ok(None);\n        }\n        // osascript returns paths with a trailing slash — strip it\n        Ok(Some(path.trim_end_matches('/').to_string()))\n    }\n\n    #[cfg(target_os = \"linux\")]","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-server/src/lib.rs#L111-L147","documentation":"The macOS directory picker in but-server shells out to osascript running 'choose folder'. A non-zero exit whose stderr is not one of the user-cancel markers ('User canceled' or error -128) is treated as a real failure and bailed with the exit code and stderr; an actual user cancel returns Ok(None).","triggerScenarios":"Running the pick-directory flow on macOS where osascript fails for reasons other than cancel: Automation/Finder permission denied to the hosting process, osascript missing or broken, or no WindowServer connection (SSH/headless).","commonSituations":"The app or terminal lacks Automation permission under macOS privacy settings; running the binary over SSH; corporate hardening that removes or restricts osascript.","solutions":["Grant the hosting app/terminal Automation permission for Finder/System Events (System Settings > Privacy & Security > Automation)","Run the picker from a normal GUI session, not SSH or a daemon context","Inspect the exit code and stderr embedded in the message to identify the AppleScript failure"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Rust: probe osascript availability before offering the native picker\nlet available = which::which(\"osascript\").is_ok();\noffer_picker = available && is_gui_session(); // DISPLAY/WindowServer present","typeGuard":null,"tryCatchPattern":"Cancel already maps to Ok(None); for real failures, catch the bail, show the exit code/stderr, hint at System Settings > Privacy & Security > Automation, and fall back to a manual path input field.","preventionTips":["Request Automation permission early in app setup on macOS","Do not invoke the picker from SSH or daemon contexts; detect GUI sessions first"],"tags":["macos","osascript","directory-picker","permissions","native-dialog"],"backgroundTag":"native-dialog-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}