Prevent exec from permanently redirecting stderr#849
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes an issue in zsh-autocomplete’s async initialization where a redirection intended to silence an fd-close error accidentally silences stderr for the entire interactive shell session (Fix #848).
Changes:
- Scope
2>/dev/nullto a command group when closing the previous async fd, sostderris restored afterward.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| # Unhook and close the previous fd. (If it no longer exists, then this will fail harmlessly.) | ||
| builtin zle -F "$_autocomplete__async_fd" 2>/dev/null | ||
| exec {_autocomplete__async_fd}<&- 2>/dev/null | ||
| { exec {_autocomplete__async_fd}<&- } 2>/dev/null |
There was a problem hiding this comment.
From the docs:
A list is a sequence of zero or more sublists, in which each sublist is terminated by ‘;’, ‘&’, ‘&|’, ‘&!’, or a newline. This terminator may optionally be omitted from the last sublist in the list when the list appears as a complex command inside ‘(...)’ or ‘{...}’. When a sublist is terminated by ‘;’ or newline, the shell waits for it to finish before executing the next sublist. If a sublist is terminated by a ‘&’, ‘&|’, or ‘&!’, the shell executes the last pipeline in it in the background, and does not wait for it to finish (note the difference from other shells which execute the whole sublist in the background). A backgrounded pipeline returns a status of zero.
|
Just found your PR (while stumbling upon the same issue and trying to fix here) by ultra safely closing the file descriptor: .autocomplete:async:close-fd-safely() {
local fd=$1
local saved_stderr
exec {saved_stderr}>&2
exec 2>/dev/null
exec {fd}<&-
exec 2>&$saved_stderr
exec {saved_stderr}>&-
}
...
# exec {_autocomplete__async_fd}<&- 2>/dev/null
.autocomplete:async:close-fd-safely "$_autocomplete__async_fd"but I much much more prefer your command group approach to scope and wrap the redirect. I just tested your branch and fix and can confirm this solves the issue. To reproduce the issue: % cd $(mktemp -d)
% git clone --depth 1 -- https://github.com/marlonrichert/zsh-autocomplete.git
...
% > .zshrc <<EOF
setopt interactivecomments transientrprompt
PS1='%# '
PS2=
RPS2='%^'
fpath+=(zsh-autocomplete)
source zsh-autocomplete/zsh-autocomplete.plugin.zsh
EOF
% env -i HOME=$PWD PATH=/usr/bin:/bin TERM=$TERM zsh -d
# Check initial FD 2 status
% ls -l /proc/$$/fd/2
lrwx------ 1 xxx xxx 64 Mar 20 10:55 /proc/1725126/fd/2 -> /dev/pts/31
#
# Type any command to trigger autocomplete suggestions, e.g., type "ls " and wait for suggestions
#
# Then check FD 2 again - it may now be corrupted
% ls -l /proc/$$/fd/2
l-wx------ 1 xxx xxx 64 Mar 20 10:55 /proc/1725126/fd/2 -> /dev/null
# BUG: stderr redirected to /dev/nullor to understand the issue: % zsh -f # Start clean zsh
% ls -l /proc/$$/fd/2
lrwx------ 1 xxx xxx 64 Mar 20 10:55 /proc/1725126/fd/2 -> /dev/pts/31
% exec {testfd}< <(echo "test")
% exec {testfd}<&- 2>/dev/null
% ls -l /proc/$$/fd/2
l-wx------ 1 xxx xxx 64 Mar 20 10:55 /proc/1725126/fd/2 -> /dev/null |
Commit 2be4e7f introduced a cleanup mechanism in
.autocomplete:async:start()to unhook and close the previous fd before starting a new async operation. However, the line:permanently redirects stderr to /dev/null for the current shell, because exec without a command applies all accompanying redirections to the current shell. While the intent was
only to suppress errors from closing a stale fd, it also silenced stderr for the entire session.
Additionally, because
start()now proactively cleans up the previous fd, thecomplete:fd-widgetcallback can fire after the fd has already been unhooked and closed, producingharmless but noisy errors:
These were hidden by the same exec ...
2>/dev/nullbug.Fix
{fd}<&- callsin command groups { } so that2>/dev/nullonly applies within the group and stderr is restored afterwardcomplete:fd-widgetindividuallyFix #848
Before submitting your Pull Request (PR), please check the following:
Fixes #<bug>orResolves #<issue>in its body (not subject, that is, thefirst line) for each issue it resolves (if any).