From 3c5b7bc22d764e1f1504caa22625b2d457efe0f9 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Mon, 3 Aug 2026 21:59:33 -0700 Subject: [PATCH 1/2] Add regression test for bash prompt status preservation --- .github/workflows/ci.yml | 3 + tests/test_issue_5389_bash_prompt_status.sh | 94 +++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100755 tests/test_issue_5389_bash_prompt_status.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8624d66f683b..7e8e709661db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -271,6 +271,9 @@ jobs: - name: Validate bash prompt bootstrap composes with user PROMPT_COMMAND (starship) run: python3 tests/test_issue_5164_starship_prompt_composition.py + - name: Validate bash prompt hooks preserve command status + run: ./tests/test_issue_5389_bash_prompt_status.sh + - name: Validate feature flags (naming, owners, expiry, single use, no reuse) run: python3 scripts/lint-feature-flags.py diff --git a/tests/test_issue_5389_bash_prompt_status.sh b/tests/test_issue_5389_bash_prompt_status.sh new file mode 100755 index 000000000000..1222fd389881 --- /dev/null +++ b/tests/test_issue_5389_bash_prompt_status.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -eo pipefail + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" + +# Source the production integration in an isolated, transport-free shell. The +# prompt hook should preserve the failed command's status even on this early +# return path so the next PROMPT_COMMAND entry can observe it. +CMUX_SOCKET_PATH="" +CMUX_TAB_ID="tab-test" +CMUX_PANEL_ID="panel-test" +TMUX="" +PROMPT_COMMAND="" +source "$repo_root/Resources/shell-integration/cmux-bash-integration.bash" + +_cmux_test_cleanup() { + [[ -n "${_CMUX_GIT_ACTIVE_PWD_FILE:-}" ]] \ + && /bin/rm -f -- "$_CMUX_GIT_ACTIVE_PWD_FILE" >/dev/null 2>&1 \ + || true +} +trap _cmux_test_cleanup EXIT + +# Never let this isolated test publish its fake cmux IDs to a real tmux server. +_cmux_tmux_sync_cmux_environment() { :; } + +_cmux_status_observer() { + printf '%s\n' "$?" +} + +_assert_prompt_status_preserved() { + local route="$1" + local prompt_form="$2" + local observed="" + + unset PROMPT_COMMAND + if [[ "$prompt_form" == "array" ]]; then + PROMPT_COMMAND=("_cmux_prompt_command" "_cmux_status_observer") + observed="$( + set +e + false + eval "${PROMPT_COMMAND[0]}" + eval "${PROMPT_COMMAND[1]}" + )" + else + PROMPT_COMMAND="_cmux_prompt_command;_cmux_status_observer" + observed="$( + set +e + false + eval "$PROMPT_COMMAND" + )" + fi + + if [[ "$observed" != "1" ]]; then + printf 'FAIL: %s (%s) downstream hook observed status %q, expected 1\n' \ + "$route" "$prompt_form" "$observed" >&2 + exit 1 + fi +} + +_assert_route_preserves_status() { + local route="$1" + _assert_prompt_status_preserved "$route" string + _assert_prompt_status_preserved "$route" array +} + +# Exercise the first early return with the production transport checks. +_assert_route_preserves_status "no transport" + +# Stub only the integration's external effects so the remaining cases can +# deterministically drive every other return path without a live cmux process. +_cmux_socket_is_unix() { return 0; } +_cmux_report_tty_once() { :; } +_cmux_report_shell_activity_state() { :; } +_cmux_reset_terminal_keyboard_protocols() { :; } +_cmux_set_git_active_pwd() { :; } +_cmux_stop_pr_poll_loop() { :; } +_cmux_clear_pr_command_hint_file() { :; } +_cmux_ports_kick() { :; } +_cmux_send_bg() { :; } + +CMUX_TAB_ID="" +_assert_route_preserves_status "missing tab" + +CMUX_TAB_ID="tab-test" +CMUX_PANEL_ID="" +_CMUX_TTY_NAME="tty-test" +_assert_route_preserves_status "missing panel" + +CMUX_PANEL_ID="panel-test" +CMUX_NO_GIT_WATCH=1 +_CMUX_PWD_LAST_PWD="$PWD" +_assert_route_preserves_status "normal completion" + +printf 'PASS: downstream prompt hooks preserved status across all routes and forms\n' From ee15d36fcff35e056a61a939e5dd0f47cb95e59f Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Mon, 3 Aug 2026 22:14:58 -0700 Subject: [PATCH 2/2] Preserve command status through bash prompt hook --- Resources/shell-integration/cmux-bash-integration.bash | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Resources/shell-integration/cmux-bash-integration.bash b/Resources/shell-integration/cmux-bash-integration.bash index c2af5696ec53..5cf49f1685ad 100644 --- a/Resources/shell-integration/cmux-bash-integration.bash +++ b/Resources/shell-integration/cmux-bash-integration.bash @@ -1607,13 +1607,15 @@ _cmux_bash_preexec_hook_subshell() { } _cmux_prompt_command() { + # This must stay the first command: every return path preserves the user's + # exit status for downstream PROMPT_COMMAND hooks. local last_status=$? _cmux_tmux_sync_cmux_environment local cmux_has_unix_socket=0 _cmux_socket_is_unix && cmux_has_unix_socket=1 - (( cmux_has_unix_socket )) || _cmux_has_port_scan_transport || return 0 - [[ -n "$CMUX_TAB_ID" ]] || return 0 + (( cmux_has_unix_socket )) || _cmux_has_port_scan_transport || return "$last_status" + [[ -n "$CMUX_TAB_ID" ]] || return "$last_status" if [[ -z "$_CMUX_TTY_NAME" ]]; then local t @@ -1638,7 +1640,7 @@ _cmux_prompt_command() { _cmux_report_pwd_via_relay "$pwd" && _CMUX_PWD_LAST_PWD="$pwd" fi else - [[ -n "$CMUX_PANEL_ID" ]] || return 0 + [[ -n "$CMUX_PANEL_ID" ]] || return "$last_status" fi _cmux_set_git_active_pwd "$pwd" @@ -1752,6 +1754,7 @@ _cmux_prompt_command() { if (( now - _CMUX_PORTS_LAST_RUN >= 10 )); then _cmux_ports_kick refresh fi + return "$last_status" } _cmux_install_prompt_command() {