Repository navigation
Reduce shell integration prompt latency #2109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,27 +1,36 @@ | ||||||
| # cmux shell integration for bash | ||||||
|
|
||||||
| _cmux_send() { | ||||||
| local payload="$1" | ||||||
| # Cache which send tool is available to avoid repeated PATH lookups. | ||||||
| _CMUX_SEND_TOOL="" | ||||||
| _cmux_detect_send_tool() { | ||||||
| if command -v ncat >/dev/null 2>&1; then | ||||||
| printf '%s\n' "$payload" | ncat -w 1 -U "$CMUX_SOCKET_PATH" --send-only | ||||||
| _CMUX_SEND_TOOL=ncat | ||||||
| elif command -v socat >/dev/null 2>&1; then | ||||||
| printf '%s\n' "$payload" | socat -T 1 - "UNIX-CONNECT:$CMUX_SOCKET_PATH" >/dev/null 2>&1 | ||||||
| _CMUX_SEND_TOOL=socat | ||||||
| elif command -v nc >/dev/null 2>&1; then | ||||||
| # Some nc builds don't support unix sockets, but keep as a last-ditch fallback. | ||||||
| # | ||||||
| # Important: macOS/BSD nc will often wait for the peer to close the socket | ||||||
| # after it has finished writing. cmux keeps the connection open, so | ||||||
| # a plain `nc -U` can hang indefinitely and leak background processes. | ||||||
| # | ||||||
| # Prefer flags that guarantee we exit after sending, and fall back to a | ||||||
| # short timeout so we never block sidebar updates. | ||||||
| if printf '%s\n' "$payload" | nc -N -U "$CMUX_SOCKET_PATH" >/dev/null 2>&1; then | ||||||
| : | ||||||
| else | ||||||
| printf '%s\n' "$payload" | nc -w 1 -U "$CMUX_SOCKET_PATH" >/dev/null 2>&1 || true | ||||||
| fi | ||||||
| _CMUX_SEND_TOOL=nc | ||||||
| fi | ||||||
| } | ||||||
| # Detection deferred to after _cmux_fix_path (end of file). | ||||||
|
|
||||||
| _cmux_send() { | ||||||
| local payload="$1" | ||||||
| case "$_CMUX_SEND_TOOL" in | ||||||
| ncat) | ||||||
| printf '%s\n' "$payload" | ncat -w 1 -U "$CMUX_SOCKET_PATH" --send-only | ||||||
| ;; | ||||||
| socat) | ||||||
| printf '%s\n' "$payload" | socat -T 1 - "UNIX-CONNECT:$CMUX_SOCKET_PATH" >/dev/null 2>&1 | ||||||
| ;; | ||||||
| nc) | ||||||
| if printf '%s\n' "$payload" | nc -N -U "$CMUX_SOCKET_PATH" >/dev/null 2>&1; then | ||||||
| : | ||||||
| else | ||||||
| printf '%s\n' "$payload" | nc -w 1 -U "$CMUX_SOCKET_PATH" >/dev/null 2>&1 || true | ||||||
| fi | ||||||
| ;; | ||||||
| esac | ||||||
| } | ||||||
|
|
||||||
| _cmux_restore_scrollback_once() { | ||||||
| local path="${CMUX_RESTORE_SCROLLBACK_FILE:-}" | ||||||
|
|
@@ -271,6 +280,7 @@ _cmux_clear_pr_for_panel() { | |||||
| [[ -S "$CMUX_SOCKET_PATH" ]] || return 0 | ||||||
| [[ -n "$CMUX_TAB_ID" ]] || return 0 | ||||||
| [[ -n "$CMUX_PANEL_ID" ]] || return 0 | ||||||
| # Synchronous: must arrive before the next report_pr from the poll loop. | ||||||
| _cmux_send "clear_pr --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -445,9 +455,10 @@ _cmux_run_pr_probe_with_timeout() { | |||||
|
|
||||||
| _cmux_stop_pr_poll_loop() { | ||||||
| if [[ -n "$_CMUX_PR_POLL_PID" ]]; then | ||||||
| # Use SIGKILL directly to avoid blocking sleep in preexec. | ||||||
| # The poll loop is lightweight and safe to kill abruptly. | ||||||
| _cmux_kill_process_tree "$_CMUX_PR_POLL_PID" KILL | ||||||
| # Process-group kill: background jobs are process-group leaders, so | ||||||
| # negative PID kills the loop + all descendants (gh, sleep) without | ||||||
| # the synchronous /bin/ps + awk of tree-kill (~5-13ms). | ||||||
| kill -KILL -- -"$_CMUX_PR_POLL_PID" 2>/dev/null || true | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Process-group kill assumes Prompt for AI agents
Suggested change
|
||||||
| _CMUX_PR_POLL_PID="" | ||||||
| fi | ||||||
| } | ||||||
|
|
@@ -702,4 +713,6 @@ _cmux_fix_path() { | |||||
| _cmux_fix_path | ||||||
| unset -f _cmux_fix_path | ||||||
|
|
||||||
| _cmux_detect_send_tool | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The transport is detected once at startup ( Useful? React with 👍 / 👎. |
||||||
|
|
||||||
| _cmux_install_prompt_command | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,29 @@ | ||
| # cmux shell integration for zsh | ||
| # Injected automatically — do not source manually | ||
|
|
||
| # Prefer zsh/net/unix for socket sends (no fork, ~0.2ms per send vs ~3ms | ||
| # for fork+exec of ncat/socat/nc). Falls back to external tools if the | ||
| # module is unavailable. | ||
| typeset -g _CMUX_HAS_ZSOCKET=0 | ||
| if zmodload zsh/net/unix 2>/dev/null; then | ||
| _CMUX_HAS_ZSOCKET=1 | ||
| fi | ||
|
|
||
| _cmux_send() { | ||
| local payload="$1" | ||
| if (( _CMUX_HAS_ZSOCKET )); then | ||
| local fd | ||
| zsocket "$CMUX_SOCKET_PATH" 2>/dev/null || return 1 | ||
| fd=$REPLY | ||
| print -u $fd -r -- "$payload" 2>/dev/null | ||
| exec {fd}>&- 2>/dev/null | ||
| return 0 | ||
| fi | ||
| if command -v ncat >/dev/null 2>&1; then | ||
| print -r -- "$payload" | ncat -w 1 -U "$CMUX_SOCKET_PATH" --send-only | ||
| elif command -v socat >/dev/null 2>&1; then | ||
| print -r -- "$payload" | socat -T 1 - "UNIX-CONNECT:$CMUX_SOCKET_PATH" >/dev/null 2>&1 | ||
| elif command -v nc >/dev/null 2>&1; then | ||
| # Some nc builds don't support unix sockets, but keep as a last-ditch fallback. | ||
| # | ||
| # Important: macOS/BSD nc will often wait for the peer to close the socket | ||
| # after it has finished writing. cmux keeps the connection open, so | ||
| # a plain `nc -U` can hang indefinitely and leak background processes. | ||
| # | ||
| # Prefer flags that guarantee we exit after sending, and fall back to a | ||
| # short timeout so we never block sidebar updates. | ||
| if print -r -- "$payload" | nc -N -U "$CMUX_SOCKET_PATH" >/dev/null 2>&1; then | ||
| : | ||
| else | ||
|
|
@@ -24,6 +32,16 @@ _cmux_send() { | |
| fi | ||
| } | ||
|
|
||
| # Fire-and-forget send: synchronous when zsocket is available (fast, no fork), | ||
| # backgrounded otherwise. | ||
| _cmux_send_bg() { | ||
| if (( _CMUX_HAS_ZSOCKET )); then | ||
| _cmux_send "$1" | ||
| else | ||
| { _cmux_send "$1" } >/dev/null 2>&1 &! | ||
| fi | ||
| } | ||
|
|
||
| _cmux_restore_scrollback_once() { | ||
| local path="${CMUX_RESTORE_SCROLLBACK_FILE:-}" | ||
| [[ -n "$path" ]] || return 0 | ||
|
|
@@ -337,9 +355,7 @@ _cmux_report_tty_once() { | |
| [[ -n "$payload" ]] || return 0 | ||
|
|
||
| _CMUX_TTY_REPORTED=1 | ||
| { | ||
| _cmux_send "$payload" | ||
| } >/dev/null 2>&1 &! | ||
| _cmux_send_bg "$payload" | ||
| } | ||
|
|
||
| _cmux_report_shell_activity_state() { | ||
|
|
@@ -350,9 +366,7 @@ _cmux_report_shell_activity_state() { | |
| [[ -n "$CMUX_PANEL_ID" ]] || return 0 | ||
| [[ "$_CMUX_SHELL_ACTIVITY_LAST" == "$state" ]] && return 0 | ||
| _CMUX_SHELL_ACTIVITY_LAST="$state" | ||
| { | ||
| _cmux_send "report_shell_state $state --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||
| } >/dev/null 2>&1 &! | ||
| _cmux_send_bg "report_shell_state $state --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||
| } | ||
|
|
||
| _cmux_ports_kick() { | ||
|
|
@@ -362,9 +376,7 @@ _cmux_ports_kick() { | |
| [[ -n "$CMUX_TAB_ID" ]] || return 0 | ||
| [[ -n "$CMUX_PANEL_ID" ]] || return 0 | ||
| _CMUX_PORTS_LAST_RUN=$EPOCHSECONDS | ||
| { | ||
| _cmux_send "ports_kick --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||
| } >/dev/null 2>&1 &! | ||
| _cmux_send_bg "ports_kick --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||
| } | ||
|
|
||
| _cmux_report_git_branch_for_path() { | ||
|
|
@@ -392,7 +404,7 @@ _cmux_clear_pr_for_panel() { | |
| [[ -S "$CMUX_SOCKET_PATH" ]] || return 0 | ||
| [[ -n "$CMUX_TAB_ID" ]] || return 0 | ||
| [[ -n "$CMUX_PANEL_ID" ]] || return 0 | ||
| _cmux_send "clear_pr --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||
| _cmux_send_bg "clear_pr --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| _cmux_pr_output_indicates_no_pull_request() { | ||
|
|
@@ -567,9 +579,10 @@ _cmux_run_pr_probe_with_timeout() { | |
|
|
||
| _cmux_stop_pr_poll_loop() { | ||
| if [[ -n "$_CMUX_PR_POLL_PID" ]]; then | ||
| # Use SIGKILL directly to avoid blocking sleep in preexec. | ||
| # The poll loop is lightweight and safe to kill abruptly. | ||
| _cmux_kill_process_tree "$_CMUX_PR_POLL_PID" KILL | ||
| # Process-group kill: background jobs are process-group leaders, so | ||
| # negative PID kills the loop + all descendants (gh, sleep) without | ||
| # the synchronous /bin/ps + awk of tree-kill (~5-13ms). | ||
| kill -KILL -- -"$_CMUX_PR_POLL_PID" 2>/dev/null || true | ||
| _CMUX_PR_POLL_PID="" | ||
| fi | ||
| } | ||
|
Comment on lines
580
to
588
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In normal interactive use this is fine (one The same change exists in the bash integration at line 461. If the trade-off is intentional (it is explicitly called out in the PR description), consider at minimum capping the orphan window by signaling the intermediate subshell group. For example, if |
||
|
|
@@ -682,7 +695,7 @@ _cmux_precmd() { | |
| _cmux_report_shell_activity_state prompt | ||
|
|
||
| # Handle cases where Ghostty integration initializes after this file. | ||
| _cmux_patch_ghostty_semantic_redraw | ||
| (( _CMUX_GHOSTTY_SEMANTIC_PATCHED )) || _cmux_patch_ghostty_semantic_redraw | ||
|
|
||
| if [[ -z "$_CMUX_TTY_NAME" ]]; then | ||
| local t | ||
|
|
@@ -717,11 +730,8 @@ _cmux_precmd() { | |
| # This is also the simplest way to test sidebar directory behavior end-to-end. | ||
| if [[ "$pwd" != "$_CMUX_PWD_LAST_PWD" ]]; then | ||
| _CMUX_PWD_LAST_PWD="$pwd" | ||
| { | ||
| # Quote to preserve spaces. | ||
| local qpwd="${pwd//\"/\\\"}" | ||
| _cmux_send "report_pwd \"${qpwd}\" --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||
| } >/dev/null 2>&1 &! | ||
| local qpwd="${pwd//\"/\\\"}" | ||
| _cmux_send_bg "report_pwd \"${qpwd}\" --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" | ||
| fi | ||
|
|
||
| # Git branch/dirty: update immediately on directory change, otherwise every ~3s. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The new synchronous
clear_prsend no longer suppresses transport stderr, soncaterrors can print into the prompt.Prompt for AI agents