{"record":{"id":"b435aea1eb110ffb","repo":"gitbutlerapp/gitbutler","slug":"would-probably-need-to-run-ln-sf-unix-lin","errorCode":null,"errorMessage":"Would probably need to run \"ln -sf '{}' '{UNIX_LINK_PATH}'\"{privilege}","messagePattern":"Would probably need to run \"ln -sf '(.+?)' '(.+?)'\"(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-action/src/cli.rs","lineNumber":100,"sourceCode":"            // dedicated Code so the frontend can react based on the code\n            // rather than matching on an English message.\n            Err(\n                anyhow!(\"osascript exited with status 1\").context(ErrorContext::new_static(\n                    Code::CliInstallCancelled,\n                    \"CLI install cancelled\",\n                )),\n            )\n        } else {\n            Err(anyhow!(\n                \"osascript exited with status {}\",\n                status\n                    .code()\n                    .map(|c| c.to_string())\n                    .unwrap_or_else(|| \"unknown\".into())\n            ))\n        }\n    } else {\n        Err(anyhow!(\n            \"Would probably need to run \\\"ln -sf '{}' '{UNIX_LINK_PATH}'\\\"{privilege}\",\n            cli_path.display(),\n            privilege = if can_elevate_privileges {\n                \" with root permissions\"\n            } else {\n                \"\"\n            }\n        ))\n    }\n}\n\nfn ensure_cli_path_exists_prior_to_link(cli_path: &std::path::Path) -> anyhow::Result<()> {\n    if cli_path.exists() {\n        return Ok(());\n    }\n    bail!(\"Run `CARGO_TARGET_DIR=$PWD/target/tauri cargo build -p but` to build the `but` binary\")\n}\n","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but-action/src/cli.rs#L82-L118","documentation":"do_install_cli returns this error when it could not create the /usr/local/bin/but symlink itself and cannot (Linux, or InstallMode::CurrentUserOnly on macOS) or is not allowed to elevate via osascript. It is advisory: the message prints the exact `ln -sf` command the user would need to run manually, optionally suffixed with 'with root permissions'.","triggerScenarios":"do_install_cli on Linux, or on macOS with InstallMode::CurrentUserOnly, after std::os::unix::fs::symlink to /usr/local/bin/but failed (directory not user-writable, or the earlier remove+symlink retry failed) — the final else branch emits the manual `ln -sf` hint.","commonSituations":"Linux systems where /usr/local/bin is root-owned; CI containers running the app as non-root; macOS users choosing current-user-only installation; /usr/local/bin missing entirely.","solutions":["Run the exact command from the error text (it contains the real paths): sudo ln -sf '<cli_path>' /usr/local/bin/but.","Prefer a user-writable directory already in PATH: mkdir -p ~/.local/bin && ln -sf '<cli_path>' ~/.local/bin/but.","Verify the '<cli_path>' binary exists first — the installer separately bails with a cargo build hint if it does not.","If /usr/local/bin/but exists as a regular file (not a symlink), remove it: the installer refuses to overwrite non-symlinks."],"exampleFix":"sudo ln -sf \"$PWD/target/tauri/debug/but\" /usr/local/bin/but\n# or user-local:\nmkdir -p ~/.local/bin && ln -sf \"$PWD/target/tauri/debug/but\" ~/.local/bin/but && hash -r\nbut --version","handlingStrategy":"fallback","validationCode":"use std::path::Path;\n\nfn pick_writable_link_dir() -> std::path::PathBuf {\n    let usr_local = Path::new(\"/usr/local/bin\");\n    let probe = usr_local.join(\".but-probe\");\n    if usr_local.is_dir() && std::fs::write(&probe, b\"\").is_ok() {\n        let _ = std::fs::remove_file(&probe);\n        return usr_local.to_path_buf();\n    }\n    std::env::var_os(\"HOME\").map(|h| Path::new(&h).join(\".local/bin\")).unwrap()\n}","typeGuard":null,"tryCatchPattern":"match do_install_cli(InstallMode::CurrentUserOnly) {\n    Ok(()) => Ok(()),\n    Err(err) => {\n        // The error text IS the manual instruction: show it as copy-pasteable steps.\n        show_manual_install_hint(&err.to_string());\n        Ok(())\n    }\n}","preventionTips":["Pre-create a user-writable bin directory on PATH before install.","Never install onto a pre-existing regular file at /usr/local/bin/but.","Surface the printed ln -sf command in the UI whenever this error fires."],"tags":["linux","cli-install","symlink","path-permissions"],"backgroundTag":"symlink-permission-denied","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}