fix: Atuin hangs when attempting to spawn daemon from Ctrl+R invocation (#3502)
Codespell / Check for spelling errors (push) Has been cancelled
Nix / check (push) Has been cancelled
Nix / build-test (push) Has been cancelled
Rust / unit-test (macos-14) (push) Has been cancelled
Install / install (macos-14) (push) Has been cancelled
Rust / build (depot-ubuntu-24.04) (push) Has been cancelled
Rust / build (macos-14) (push) Has been cancelled
Rust / build (windows-latest) (push) Has been cancelled
Rust / cross-compile (x86_64-unknown-illumos) (push) Has been cancelled
Rust / unit-test (depot-ubuntu-24.04) (push) Has been cancelled
Rust / unit-test (windows-latest) (push) Has been cancelled
Rust / clippy (push) Has been cancelled
Rust / format (push) Has been cancelled
Shellcheck / shellcheck (push) Has been cancelled
build-docker / publish (push) Has been cancelled
Install / install (depot-ubuntu-24.04) (push) Has been cancelled
Rust / check (depot-ubuntu-24.04) (push) Has been cancelled
Rust / check (macos-14) (push) Has been cancelled
Rust / check (windows-latest) (push) Has been cancelled
Rust / integration-test (push) Has been cancelled

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
This commit is contained in:
Michelle Tilley
2026-05-27 14:42:50 -07:00
committed by GitHub
parent 6ecbc8a5d8
commit 4475e17c00
4 changed files with 46 additions and 15 deletions
@@ -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 !self.inline_mode {
execute!(self.writer, terminal::LeaveAlternateScreen).unwrap();
if let Err(e) = execute!(self.writer, PopKeyboardEnhancementFlags) {
tracing::error!(?e, "Failed to pop keyboard enhancement flags");
}
if !self.no_mouse {
execute!(self.writer, event::DisableMouseCapture).unwrap();
}
execute!(self.writer, event::DisableBracketedPaste).unwrap();
terminal::disable_raw_mode().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
&& let Err(e) = execute!(self.writer, event::DisableMouseCapture)
{
tracing::error!(?e, "Failed to disable mouse capture");
}
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");
}
}
}
+5 -2
View File
@@ -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
+12 -2
View File
@@ -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
+8 -2
View File
@@ -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