Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 6 additions & 3 deletions Resources/shell-integration/cmux-bash-integration.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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() {
Expand Down
94 changes: 94 additions & 0 deletions tests/test_issue_5389_bash_prompt_status.sh
Original file line number Diff line number Diff line change
@@ -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'
Loading