Skip to content
Open
Changes from 1 commit
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
132 changes: 77 additions & 55 deletions Resources/shell-integration/cmux-bash-integration.bash
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ _cmux_relay_rpc_bg() {
local relay_cli=""
_cmux_socket_uses_remote_relay || return 1
relay_cli="$(_cmux_relay_cli_path)" || return 1
{
# Isolated process group: see issue #2105 (Ctrl-Z stopped-job SIGHUP).
( set -m; {
"$relay_cli" rpc "$method" "$params" >/dev/null 2>&1 || true
} >/dev/null 2>&1 &
disown 2>/dev/null || true
} >/dev/null 2>&1 & )
}

_cmux_relay_rpc() {
Expand Down Expand Up @@ -380,9 +380,13 @@ _cmux_report_tty_once() {
payload="$(_cmux_report_tty_payload)"
[[ -n "$payload" ]] || return 0
_CMUX_TTY_REPORTED=1
{
# Run in its own process group so bash's stopped-job cleanup
# (triggered by Ctrl-Z on a foreground job) cannot SIGHUP this
# background subshell and the suspended job along with it.
# See https://github.com/manaflow-ai/cmux/issues/2105
( set -m; {
_cmux_send "$payload"
} >/dev/null 2>&1 & disown
} >/dev/null 2>&1 & )
else
[[ -n "$_CMUX_TTY_NAME" ]] || return 0
# Keep the first relay TTY report synchronous so the server can resolve
Expand All @@ -400,9 +404,10 @@ _cmux_report_shell_activity_state() {
[[ -n "$CMUX_PANEL_ID" ]] || return 0
[[ "$_CMUX_SHELL_ACTIVITY_LAST" == "$state" ]] && return 0
_CMUX_SHELL_ACTIVITY_LAST="$state"
{
# Isolated process group: see issue #2105 (Ctrl-Z stopped-job SIGHUP).
( set -m; {
_cmux_send "report_shell_state $state --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
} >/dev/null 2>&1 & disown
} >/dev/null 2>&1 & )
}

_cmux_ports_kick() {
Expand All @@ -416,9 +421,10 @@ _cmux_ports_kick() {
fi
_CMUX_PORTS_LAST_RUN="$(_cmux_now)"
if _cmux_socket_is_unix; then
{
# Isolated process group: see issue #2105 (Ctrl-Z stopped-job SIGHUP).
( set -m; {
_cmux_send "ports_kick --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID --reason=$reason"
} >/dev/null 2>&1 & disown
} >/dev/null 2>&1 & )
else
_cmux_ports_kick_via_relay "$reason"
fi
Expand Down Expand Up @@ -514,9 +520,10 @@ _cmux_emit_pr_command_hint() {
local quoted_target="${_CMUX_LAST_PR_TARGET//\"/\\\"}"
payload+=" --target=\"$quoted_target\""
fi
{
# Isolated process group: see issue #2105 (Ctrl-Z stopped-job SIGHUP).
( set -m; {
_cmux_send "$payload"
} >/dev/null 2>&1 & disown
} >/dev/null 2>&1 & )
_CMUX_LAST_PR_ACTION=""
_CMUX_LAST_PR_TARGET=""
}
Expand Down Expand Up @@ -769,10 +776,14 @@ _cmux_run_pr_probe_with_timeout() {
started_at="$(_cmux_now)"
now=$started_at

(
_cmux_report_pr_for_path "$repo_path" "$force_probe"
) &
probe_pid=$!
# Isolated process group: see issue #2105 (Ctrl-Z stopped-job SIGHUP).
probe_pid=$(
set -m
(
_cmux_report_pr_for_path "$repo_path" "$force_probe"
) &
echo $!
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested set -m lets probes escape process-group kill

Medium Severity

_cmux_run_pr_probe_with_timeout uses set -m to give the probe its own process group, but it only runs inside the poll loop (which already has its own process group from _cmux_start_pr_poll_loop). When _cmux_halt_pr_poll_loop does kill -KILL -- -"$_CMUX_PR_POLL_PID", the probe is in a different process group and survives. The timeout-enforcement loop dies with the poll loop, leaving an unsupervised gh process. Temp status files also leak. Previously the probe inherited the poll loop's process group and was killed together with it.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9039a6a. Configure here.


while kill -0 "$probe_pid" >/dev/null 2>&1; do
sleep 1
Expand Down Expand Up @@ -835,31 +846,36 @@ _cmux_start_pr_poll_loop() {
fi
_CMUX_PR_POLL_PWD="$watch_pwd"

{
local signal_path=""
signal_path="$(_cmux_pr_force_signal_path 2>/dev/null || true)"
while :; do
kill -0 "$watch_shell_pid" 2>/dev/null || break
local force_probe=0
if [[ -n "$signal_path" && -f "$signal_path" ]]; then
force_probe=1
/bin/rm -f -- "$signal_path" >/dev/null 2>&1 || true
fi
_cmux_run_pr_probe_with_timeout "$watch_pwd" "$force_probe" || true

local slept=0
while (( slept < interval )); do
kill -0 "$watch_shell_pid" 2>/dev/null || exit 0
# Isolated process group: see issue #2105 (Ctrl-Z stopped-job SIGHUP).
# `set -m` inside the command substitution gives the inner `&` its own
# process group; `echo $!` relays the PID so we can still kill -0 / kill it.
_CMUX_PR_POLL_PID=$(
set -m
{
local signal_path=""
signal_path="$(_cmux_pr_force_signal_path 2>/dev/null || true)"
while :; do
kill -0 "$watch_shell_pid" 2>/dev/null || break
local force_probe=0
if [[ -n "$signal_path" && -f "$signal_path" ]]; then
break
force_probe=1
/bin/rm -f -- "$signal_path" >/dev/null 2>&1 || true
fi
sleep 1
slept=$(( slept + 1 ))
_cmux_run_pr_probe_with_timeout "$watch_pwd" "$force_probe" || true

local slept=0
while (( slept < interval )); do
kill -0 "$watch_shell_pid" 2>/dev/null || exit 0
if [[ -n "$signal_path" && -f "$signal_path" ]]; then
break
fi
sleep 1
slept=$(( slept + 1 ))
done
done
done
} >/dev/null 2>&1 &
_CMUX_PR_POLL_PID=$!
disown "$_CMUX_PR_POLL_PID" 2>/dev/null || disown
} >/dev/null 2>&1 &
echo $!
)
}

_cmux_bash_cleanup() {
Expand Down Expand Up @@ -1004,10 +1020,11 @@ _cmux_prompt_command() {
# CWD: keep the app in sync with the actual shell directory.
if [[ "$pwd" != "$_CMUX_PWD_LAST_PWD" ]]; then
_CMUX_PWD_LAST_PWD="$pwd"
{
# Isolated process group: see issue #2105 (Ctrl-Z stopped-job SIGHUP).
( set -m; {
local qpwd="${pwd//\"/\\\"}"
_cmux_send "report_pwd \"${qpwd}\" --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
} >/dev/null 2>&1 & disown
} >/dev/null 2>&1 & )
fi

# Branch can change via aliases/tools while an older probe is still in flight.
Expand Down Expand Up @@ -1051,22 +1068,27 @@ _cmux_prompt_command() {
if [[ -z "$_CMUX_GIT_JOB_PID" ]] || ! kill -0 "$_CMUX_GIT_JOB_PID" 2>/dev/null; then
_CMUX_GIT_LAST_PWD="$pwd"
_CMUX_GIT_LAST_RUN=$now
{
# Skip git operations if not in a git repository to avoid TCC prompts
git rev-parse --git-dir >/dev/null 2>&1 || return 0
local branch dirty_opt=""
branch=$(git branch --show-current 2>/dev/null)
if [[ -n "$branch" ]]; then
local first
first=$(git status --porcelain -uno 2>/dev/null | head -1)
[[ -n "$first" ]] && dirty_opt="--status=dirty"
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
else
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
fi
} >/dev/null 2>&1 &
_CMUX_GIT_JOB_PID=$!
disown
# Isolated process group: see issue #2105 (Ctrl-Z stopped-job SIGHUP).
# `set -m` inside the command substitution gives the inner `&` its own
# process group; `echo $!` relays the PID so kill -0 / kill still work.
_CMUX_GIT_JOB_PID=$(
set -m
{
# Skip git operations if not in a git repository to avoid TCC prompts
git rev-parse --git-dir >/dev/null 2>&1 || exit 0
local branch dirty_opt=""
branch=$(git branch --show-current 2>/dev/null)
if [[ -n "$branch" ]]; then
local first
first=$(git status --porcelain -uno 2>/dev/null | head -1)
[[ -n "$first" ]] && dirty_opt="--status=dirty"
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
else
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
fi
} >/dev/null 2>&1 &
echo $!
)
_CMUX_GIT_JOB_STARTED_AT=$now
fi

Expand Down
Loading