aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichelle Tilley <michelle@michelletilley.net>2026-05-27 14:42:50 -0700
committerGitHub <noreply@github.com>2026-05-27 14:42:50 -0700
commit4475e17c0020ace563d8fbf8a784336041b36d86 (patch)
tree1217fa8867daaceada1b338759c94d52a3edd44b
parentAllow overriding model with env var (#3493) (diff)
downloadatuin-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.rs26
-rw-r--r--crates/atuin/src/shell/atuin.bash7
-rw-r--r--crates/atuin/src/shell/atuin.fish14
-rw-r--r--crates/atuin/src/shell/atuin.zsh10
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