diff --git a/scripts/launcher-common.sh b/scripts/launcher-common.sh index df60813..dc7409e 100644 --- a/scripts/launcher-common.sh +++ b/scripts/launcher-common.sh @@ -42,7 +42,8 @@ log_session_env() { QT_IM_MODULE \ CLAUDE_USE_WAYLAND \ CLAUDE_TITLEBAR_STYLE \ - CLAUDE_GTK_IM_MODULE + CLAUDE_GTK_IM_MODULE \ + CLAUDE_DISABLE_GPU do log_message " $key=${!key:-}" done @@ -140,10 +141,24 @@ build_electron_args() { loginctl show-session "$XDG_SESSION_ID" \ -p Type --value 2>/dev/null ) + # Track GPU-disable decision so XRDP and CLAUDE_DISABLE_GPU don't + # stack duplicate flags. Either signal is sufficient. + local _disable_gpu=false if [[ -n ${XRDP_SESSION:-} || $rdp_session_type == xrdp ]]; then - electron_args+=('--disable-gpu' '--disable-software-rasterizer') + _disable_gpu=true log_message 'XRDP session detected - GPU compositing disabled' fi + # CLAUDE_DISABLE_GPU=1: opt-in workaround for users hitting the + # Chromium GPU process FATAL exhaustion (#583). The same upstream + # behaviour is reachable via Settings → disable hardware + # acceleration; this lets users persist it via the env without + # having to reach the Settings UI through repeated crashes. + if [[ ${CLAUDE_DISABLE_GPU:-} == '1' ]]; then + _disable_gpu=true + log_message 'CLAUDE_DISABLE_GPU=1 - hardware acceleration disabled' + fi + [[ $_disable_gpu == true ]] \ + && electron_args+=('--disable-gpu' '--disable-software-rasterizer') # X11 session - no special flags needed if [[ $is_wayland != true ]]; then diff --git a/tests/launcher-disable-gpu.bats b/tests/launcher-disable-gpu.bats new file mode 100644 index 0000000..cdaf4cf --- /dev/null +++ b/tests/launcher-disable-gpu.bats @@ -0,0 +1,144 @@ +#!/usr/bin/env bats +# +# launcher-disable-gpu.bats +# Tests for the CLAUDE_DISABLE_GPU env var handling in +# build_electron_args (scripts/launcher-common.sh). The var is an +# opt-in workaround for the Chromium GPU process FATAL exhaustion +# tracked in #583. CLAUDE_DISABLE_GPU=1 adds --disable-gpu and +# --disable-software-rasterizer; co-occurrence with XRDP must not +# stack duplicate flags. +# + +SCRIPT_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME}")" && pwd)" +LAUNCHER_COMMON="${SCRIPT_DIR}/../scripts/launcher-common.sh" + +setup() { + TEST_TMP=$(mktemp -d) + export TEST_TMP + + # loginctl shim — same pattern as launcher-xrdp-detection.bats. + # Defaults to a non-XRDP session so CLAUDE_DISABLE_GPU is the + # only signal in play unless a test overrides MOCK_LOGINCTL_TYPE. + mkdir -p "$TEST_TMP/bin" + cat > "$TEST_TMP/bin/loginctl" <<'SHIM' +#!/usr/bin/env bash +printf '%s\n' "${MOCK_LOGINCTL_TYPE:-x11}" +SHIM + chmod +x "$TEST_TMP/bin/loginctl" + export PATH="$TEST_TMP/bin:$PATH" + + log_file="$TEST_TMP/launcher.log" + : > "$log_file" + + unset CLAUDE_DISABLE_GPU + unset XRDP_SESSION + unset XDG_SESSION_ID + unset MOCK_LOGINCTL_TYPE + + # shellcheck disable=SC1090 + source "$LAUNCHER_COMMON" + + is_wayland=false + use_x11_on_wayland=true +} + +teardown() { + if [[ -n ${TEST_TMP:-} && -d $TEST_TMP ]]; then + rm -rf "$TEST_TMP" + fi +} + +args_contain() { + local needle="$1" + local arg + for arg in "${electron_args[@]}"; do + [[ $arg == "$needle" ]] && return 0 + done + return 1 +} + +args_count() { + local needle="$1" + local arg count=0 + for arg in "${electron_args[@]}"; do + [[ $arg == "$needle" ]] && ((count++)) + done + printf '%d' "$count" +} + +# ============================================================================= +# CLAUDE_DISABLE_GPU=1 — flags must be added +# ============================================================================= + +@test "disable-gpu: CLAUDE_DISABLE_GPU=1 adds flags + logs message" { + export CLAUDE_DISABLE_GPU=1 + + build_electron_args deb + + args_contain '--disable-gpu' + args_contain '--disable-software-rasterizer' + grep -q 'CLAUDE_DISABLE_GPU=1' "$log_file" +} + +# ============================================================================= +# Co-occurrence with XRDP — no duplicate flags +# ============================================================================= + +@test "disable-gpu: with XRDP_SESSION, flags added exactly once (no dup)" { + export CLAUDE_DISABLE_GPU=1 + export XRDP_SESSION=1 + export XDG_SESSION_ID=5 + export MOCK_LOGINCTL_TYPE=xrdp + + build_electron_args deb + + [[ "$(args_count '--disable-gpu')" -eq 1 ]] + [[ "$(args_count '--disable-software-rasterizer')" -eq 1 ]] + # Both signals should still log (independent diagnostic value), + # but only one set of flags should reach electron_args. + grep -q 'XRDP session detected' "$log_file" + grep -q 'CLAUDE_DISABLE_GPU=1' "$log_file" +} + +# ============================================================================= +# Off-states — flags must NOT be added +# ============================================================================= + +@test "disable-gpu: unset — flags NOT added" { + build_electron_args deb + + run args_contain '--disable-gpu' + [[ "$status" -ne 0 ]] + run args_contain '--disable-software-rasterizer' + [[ "$status" -ne 0 ]] +} + +@test "disable-gpu: empty string — flags NOT added" { + export CLAUDE_DISABLE_GPU='' + + build_electron_args deb + + run args_contain '--disable-gpu' + [[ "$status" -ne 0 ]] +} + +@test "disable-gpu: =0 — flags NOT added (only literal '1' opts in)" { + export CLAUDE_DISABLE_GPU=0 + + build_electron_args deb + + run args_contain '--disable-gpu' + [[ "$status" -ne 0 ]] +} + +@test "disable-gpu: =true — flags NOT added (no boolean aliases)" { + # Documents the strict equality check. If we ever add aliases, + # update this test to match. Strict-only matches the existing + # CLAUDE_USE_WAYLAND pattern. + export CLAUDE_DISABLE_GPU=true + + build_electron_args deb + + run args_contain '--disable-gpu' + [[ "$status" -ne 0 ]] +}