From aaa0bb105999b9046f998ddcbc35bdd9af14f874 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 31 Oct 2026 16:09:40 +0100 Subject: [PATCH] fix(platform): in the Flatpak a pass's shell is bash Flatpak sets SHELL to /bin/sh, the host's (zsh here) being no file of the Sdk's, and claude's Bash tool runs commands with bash or zsh. In the sandbox SHELL becomes /bin/bash unless it names a bash or zsh that is there. Found by the sandbox checks: a pass's environment had SHELL=/bin/sh. Co-Authored-By: Claude Opus 5.5 --- implementations/Crafter.Review-Platform.cpp | 11 ++++++++++- interfaces/Crafter.Review-Platform.cppm | 5 +++++ tests/Platform/main.cpp | 15 +++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/implementations/Crafter.Review-Platform.cpp b/implementations/Crafter.Review-Platform.cpp index 7e681a0..6632792 100644 --- a/implementations/Crafter.Review-Platform.cpp +++ b/implementations/Crafter.Review-Platform.cpp @@ -65,7 +65,10 @@ namespace Crafter::Review::Platform { void Init() { CurrentModeValue = fs::exists(PathsValue().flatpakInfo) ? Mode::Sandbox : Mode::Native; - if (CurrentModeValue == Mode::Sandbox) RestoreHostXdg(); + if (CurrentModeValue == Mode::Sandbox) { + RestoreHostXdg(); + RestoreShell(); + } } Mode CurrentMode() { @@ -128,6 +131,12 @@ namespace Crafter::Review::Platform { } } + void RestoreShell() { + const std::string shell = GetEnv("SHELL").value_or(""); + const bool usable = (shell.ends_with("/bash") || shell.ends_with("/zsh")) && access(shell.c_str(), X_OK) == 0; + if (!usable && access("/bin/bash", X_OK) == 0) setenv("SHELL", "/bin/bash", 1); + } + fs::path SelfExe() { std::error_code error; const fs::path exe = fs::read_symlink("/proc/self/exe", error); diff --git a/interfaces/Crafter.Review-Platform.cppm b/interfaces/Crafter.Review-Platform.cppm index 49315a6..7de36a8 100644 --- a/interfaces/Crafter.Review-Platform.cppm +++ b/interfaces/Crafter.Review-Platform.cppm @@ -51,6 +51,11 @@ export namespace Crafter::Review::Platform { // $HOME), so git reads ~/.config/git/config, Playwright finds // ~/.cache/ms-playwright, and claude and npx behave as on the host. void RestoreHostXdg(); + // Flatpak sets SHELL to /bin/sh, the host's (zsh here) being no file of + // the Sdk's. claude's Bash tool runs commands with bash or zsh, and the + // Sdk has bash: SHELL becomes /bin/bash unless it already names a bash + // or zsh that is there. + void RestoreShell(); // The absolute path of the running binary (/proc/self/exe). std::filesystem::path SelfExe(); diff --git a/tests/Platform/main.cpp b/tests/Platform/main.cpp index bb0b9af..055c1be 100644 --- a/tests/Platform/main.cpp +++ b/tests/Platform/main.cpp @@ -205,6 +205,21 @@ int main() { Check(Platform::GetEnv("XDG_CONFIG_HOME") == "/elsewhere/config", "a host XDG directory the host set comes back"); Check(Platform::GetEnv("XDG_CACHE_HOME") == Dyn::JoinPath(home, ".cache"), "one it left unset is the XDG default under $HOME"); Check(Platform::GetEnv("XDG_STATE_HOME") == Dyn::JoinPath(home, ".local/state"), "state too"); + // The shell: the sandbox's /bin/sh becomes bash; a bash or zsh that is + // there stays. + const std::optional shell = Platform::GetEnv("SHELL"); + setenv("SHELL", "/bin/sh", 1); + Platform::RestoreShell(); + const std::optional fromSh = Platform::GetEnv("SHELL"); + setenv("SHELL", "/no/such/zsh", 1); + Platform::RestoreShell(); + const std::optional fromMissing = Platform::GetEnv("SHELL"); + setenv("SHELL", "/bin/bash", 1); + Platform::RestoreShell(); + const std::optional fromBash = Platform::GetEnv("SHELL"); + if (shell) setenv("SHELL", shell->c_str(), 1); + Check(!fs::exists("/bin/bash") || (fromSh == "/bin/bash" && fromMissing == "/bin/bash" && fromBash == "/bin/bash"), + "SHELL: /bin/sh and a shell that is not there become /bin/bash; bash stays"); } {