diff options
| author | Michelle Tilley <michelle@michelletilley.net> | 2026-05-27 14:42:50 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-27 14:42:50 -0700 |
| commit | 4475e17c0020ace563d8fbf8a784336041b36d86 (patch) | |
| tree | 1217fa8867daaceada1b338759c94d52a3edd44b | |
| parent | Allow overriding model with env var (#3493) (diff) | |
| download | atuin-4475e17c0020ace563d8fbf8a784336041b36d86.zip | |
fix: Atuin hangs when attempting to spawn daemon from Ctrl+R invocation (#3502)
This PR fixes a shell hang when daemon autostart happens from the
interactive search widget.
The bash/zsh/fish integrations run `atuin search -i` under command
substitution and swap stdout/stderr so the TUI can draw to the terminal
while the selected command is captured:
```sh
3>&1 1>&2 2>&3
```
This leaves fd 3 open in the atuin search process. If search autostarts
the daemon, the spawned long-running `atuin daemon start --daemonize`
inherits fd 3, the command-substitution pipe, so the shell keeps waiting
for EOF until the daemon is killed.
The fix: close fd 3 after the swap in the non-tmux bash/zsh/fish paths:
```sh
3>&1 1>&2 2>&3 3>&-
```
Tested on zsh; I still need to confirm for bash and fish. Near as I can
tell, the other shell integrations don't need this change.
Fixes #3499
| -rw-r--r-- | crates/atuin/src/command/client/search/interactive.rs | 26 | ||||
| -rw-r--r-- | crates/atuin/src/shell/atuin.bash | 7 | ||||
| -rw-r--r-- | crates/atuin/src/shell/atuin.fish | 14 | ||||
| -rw-r--r-- | crates/atuin/src/shell/atuin.zsh | 10 |
4 files changed, 44 insertions, 13 deletions
diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs index 553f954a..28b29824 100644 --- a/crates/atuin/src/command/client/search/interactive.rs +++ b/crates/atuin/src/command/client/search/interactive.rs @@ -1532,17 +1532,29 @@ impl Stdout { impl Drop for Stdout { fn drop(&mut self) { #[cfg(not(target_os = "windows"))] - execute!(self.writer, PopKeyboardEnhancementFlags).unwrap(); + if let Err(e) = execute!(self.writer, PopKeyboardEnhancementFlags) { + tracing::error!(?e, "Failed to pop keyboard enhancement flags"); + } - if !self.inline_mode { - execute!(self.writer, terminal::LeaveAlternateScreen).unwrap(); + if !self.inline_mode + && let Err(e) = execute!(self.writer, terminal::LeaveAlternateScreen) + { + tracing::error!(?e, "Failed to leave alt screen mode"); } - if !self.no_mouse { - execute!(self.writer, event::DisableMouseCapture).unwrap(); + + if !self.no_mouse + && let Err(e) = execute!(self.writer, event::DisableMouseCapture) + { + tracing::error!(?e, "Failed to disable mouse capture"); } - execute!(self.writer, event::DisableBracketedPaste).unwrap(); - terminal::disable_raw_mode().unwrap(); + if let Err(e) = execute!(self.writer, event::DisableBracketedPaste) { + tracing::error!(?e, "Failed to disable bracketed paste"); + } + + if let Err(e) = terminal::disable_raw_mode() { + tracing::error!(?e, "Failed to disable raw mode"); + } } } diff --git a/crates/atuin/src/shell/atuin.bash b/crates/atuin/src/shell/atuin.bash index f72bfcfc..45fdced9 100644 --- a/crates/atuin/src/shell/atuin.bash +++ b/crates/atuin/src/shell/atuin.bash @@ -297,7 +297,7 @@ __atuin_search_cmd() { __atuin_tmux_popup_cleanup trap - EXIT HUP INT TERM else - ATUIN_SHELL=bash ATUIN_LOG=error ATUIN_QUERY=$READLINE_LINE atuin search "${search_args[@]}" -i 3>&1 1>&2 2>&3 + ATUIN_SHELL=bash ATUIN_LOG=error ATUIN_QUERY=$READLINE_LINE atuin search "${search_args[@]}" -i 3>&1 1>&2 2>&3 3>&- fi } @@ -329,7 +329,10 @@ __atuin_history() { READLINE_LINE="" READLINE_POINT=0 local __atuin_output - __atuin_output=$(__atuin_search_cmd "$@") + if ! __atuin_output=$(__atuin_search_cmd "$@"); then + [[ $__atuin_output ]] && printf '%s\n' "$__atuin_output" >&2 + return 1 + fi # We do nothing when the search is canceled. [[ $__atuin_output ]] || return 0 diff --git a/crates/atuin/src/shell/atuin.fish b/crates/atuin/src/shell/atuin.fish index 87e93923..ddf55f3d 100644 --- a/crates/atuin/src/shell/atuin.fish +++ b/crates/atuin/src/shell/atuin.fish @@ -81,11 +81,13 @@ function _atuin_search set -l use_tmux_popup (_atuin_tmux_popup_check) set -l ATUIN_H + set -l ATUIN_STATUS 0 if test "$use_tmux_popup" -eq 1 set -l tmpdir (mktemp -d) if not test -d "$tmpdir" # if mktemp got errors - set ATUIN_H (ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY=(commandline -b) atuin search --keymap-mode=$keymap_mode $argv -i 3>&1 1>&2 2>&3 | string collect) + set ATUIN_H (ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY=(commandline -b) atuin search --keymap-mode=$keymap_mode $argv -i 3>&1 1>&2 2>&3 3>&- | string collect) + set ATUIN_STATUS $pipestatus[1] else set -l result_file "$tmpdir/result" @@ -102,6 +104,7 @@ function _atuin_search set -l popup_height (test -n "$ATUIN_TMUX_POPUP_HEIGHT" && echo "$ATUIN_TMUX_POPUP_HEIGHT" || echo "60%") tmux display-popup -d "$cdir" -w "$popup_width" -h "$popup_height" -E -E -- \ sh -c "PATH='$PATH' ATUIN_SESSION='$ATUIN_SESSION' ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY='$query' atuin search --keymap-mode=$keymap_mode$escaped_args -i 2>'$result_file'" + set ATUIN_STATUS $status if test -f "$result_file" set ATUIN_H (cat "$result_file" | string collect) @@ -113,7 +116,14 @@ function _atuin_search # In fish 3.4 and above we can use `"$(some command)"` to keep multiple lines separate; # but to support fish 3.3 we need to use `(some command | string collect)`. # https://fishshell.com/docs/current/relnotes.html#id24 (fish 3.4 "Notable improvements and fixes") - set ATUIN_H (ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY=(commandline -b) atuin search --keymap-mode=$keymap_mode $argv -i 3>&1 1>&2 2>&3 | string collect) + set ATUIN_H (ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY=(commandline -b) atuin search --keymap-mode=$keymap_mode $argv -i 3>&1 1>&2 2>&3 3>&- | string collect) + set ATUIN_STATUS $pipestatus[1] + end + + if test "$ATUIN_STATUS" -ne 0 + test -n "$ATUIN_H"; and printf '%s\n' "$ATUIN_H" >&2 + commandline -f repaint + return "$ATUIN_STATUS" end set ATUIN_H (string trim -- $ATUIN_H | string collect) # trim whitespace diff --git a/crates/atuin/src/shell/atuin.zsh b/crates/atuin/src/shell/atuin.zsh index 8e9b975c..87f47531 100644 --- a/crates/atuin/src/shell/atuin.zsh +++ b/crates/atuin/src/shell/atuin.zsh @@ -109,7 +109,7 @@ __atuin_search_cmd() { __atuin_tmux_popup_cleanup trap - EXIT HUP INT TERM else - ATUIN_SHELL=zsh ATUIN_LOG=error ATUIN_QUERY=$BUFFER atuin search "${search_args[@]}" -i 3>&1 1>&2 2>&3 + ATUIN_SHELL=zsh ATUIN_LOG=error ATUIN_QUERY=$BUFFER atuin search "${search_args[@]}" -i 3>&1 1>&2 2>&3 3>&- fi } @@ -119,15 +119,21 @@ _atuin_search() { # swap stderr and stdout, so that the tui stuff works # TODO: not this - local output + local output __atuin_status # shellcheck disable=SC2048 output=$(__atuin_search_cmd $*) + __atuin_status=$? zle reset-prompt # re-enable bracketed paste # shellcheck disable=SC2154 echo -n ${zle_bracketed_paste[1]} >/dev/tty + if (( __atuin_status != 0 )); then + [[ -n $output ]] && print -r -- "$output" >/dev/tty + return $__atuin_status + fi + if [[ -n $output ]]; then RBUFFER="" LBUFFER=$output |
