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 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 16:09:40 +01:00
commit aaa0bb1059
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
3 changed files with 30 additions and 1 deletions

View file

@ -65,7 +65,10 @@ namespace Crafter::Review::Platform {
void Init() { void Init() {
CurrentModeValue = fs::exists(PathsValue().flatpakInfo) ? Mode::Sandbox : Mode::Native; CurrentModeValue = fs::exists(PathsValue().flatpakInfo) ? Mode::Sandbox : Mode::Native;
if (CurrentModeValue == Mode::Sandbox) RestoreHostXdg(); if (CurrentModeValue == Mode::Sandbox) {
RestoreHostXdg();
RestoreShell();
}
} }
Mode CurrentMode() { 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() { fs::path SelfExe() {
std::error_code error; std::error_code error;
const fs::path exe = fs::read_symlink("/proc/self/exe", error); const fs::path exe = fs::read_symlink("/proc/self/exe", error);

View file

@ -51,6 +51,11 @@ export namespace Crafter::Review::Platform {
// $HOME), so git reads ~/.config/git/config, Playwright finds // $HOME), so git reads ~/.config/git/config, Playwright finds
// ~/.cache/ms-playwright, and claude and npx behave as on the host. // ~/.cache/ms-playwright, and claude and npx behave as on the host.
void RestoreHostXdg(); 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). // The absolute path of the running binary (/proc/self/exe).
std::filesystem::path SelfExe(); std::filesystem::path SelfExe();

View file

@ -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_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_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"); 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<std::string> shell = Platform::GetEnv("SHELL");
setenv("SHELL", "/bin/sh", 1);
Platform::RestoreShell();
const std::optional<std::string> fromSh = Platform::GetEnv("SHELL");
setenv("SHELL", "/no/such/zsh", 1);
Platform::RestoreShell();
const std::optional<std::string> fromMissing = Platform::GetEnv("SHELL");
setenv("SHELL", "/bin/bash", 1);
Platform::RestoreShell();
const std::optional<std::string> 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");
} }
{ {