Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: [BUG] precmd not executed after Ctrl-C on some builtin



On Wed, May 13, 2026 at 1:35 AM Vincent Lefevre <vincent@xxxxxxxxxx> wrote:
>
> With zsh 5.9 and 5.9.0.3-test (1328291abbb80e90dc4473a4396daffb0e919827),
> after interrupting some commands with Ctrl-C, precmd is not executed
> before the prompt is displayed:
>
> qaa% mkfifo fifo
> qaa% precmd() { echo precmd > /dev/tty }
> precmd
> qaa% pwd
> /home/vinc17
> precmd
> qaa% pwd >> fifo
> ^C%
> qaa% read
> ^C%
> precmd
> qaa% { pwd >> fifo }
> ^C%
> precmd
> qaa%
>
> The issue occurs with commands of the form "some_builtin >> fifo".
> As seen with "read" (for which the issue does not occur), the issue
> is not just that a builtin is blocking.

With the following debug print in place,
diff --git i/Src/utils.c w/Src/utils.c
index 94844e4229..799bb099d1 100644
--- i/Src/utils.c
+++ w/Src/utils.c
@@ -1575,6 +1575,9 @@ preprompt(void)
      if (errflag)
          return;

     /* If a shell function named "precmd" exists, *
      * then execute it.                           */
+    dputs("DEBUG: queueing_enabled=%d front=%d rear=%d",
+     queueing_enabled, queue_front, queue_rear);
+
     callhookfunc("precmd", NULL, 1, NULL);
     if (errflag)
  return;

We get this output for the above tests:
% precmd() { echo precmd > /dev/tty }
 DEBUG: queueing_enabled=1 front=0 rear=0
precmd
% read
^C%
DEBUG: queueing_enabled=1 front=0 rear=0
precmd
% echo >> fifo
^C%
DEBUG: queueing_enabled=1 front=0 rear=1 <- probably not good?
% # i just pressed enter here without this comment
 DEBUG: queueing_enabled=1 front=1 rear=1
precmd

We can also notice that if we set a TRAPINT() { true } handler, the
precmd runs normally too. In signals.c, we set errflag |= ERRFLAG_INT
when there's no trap handler, otherwise we don't. So the theory is
that the signal handler runs when we execute the precmd hook, and we
abort execution, even though we just checked that errflag is 0 before
trying to run it (I manually added it in the context in the diff
above).

There's a bit in init.c that resets all errors, seen in the patch
below, and the patch makes it so that this test works as expected, and
the rest of the tests also pass. I'm not totally sure this is the
right thing to do though, I usually don't mess with signal handler
stuff.

% precmd() { echo precmd > /dev/tty }
 DEBUG: queueing_enabled=1 front=0 rear=0
precmd
% read
^C%
DEBUG: queueing_enabled=1 front=0 rear=0
precmd
% echo >> fifo
^C%
DEBUG: queueing_enabled=1 front=1 rear=1
precmd

diff --git a/Src/init.c b/Src/init.c
index 7c3b824611..a422036c71 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -117,35 +117,38 @@ loop(int toplevel, int justonce)

     queue_signals();
     pushheap();
     if (!toplevel)
         zcontext_save();
     for (;;) {
         freeheap();
         if (stophist == 3)        /* re-entry via preprompt() */
             hend(NULL);
         hbegin(1);                /* init history mech        */
         if (isset(SHINSTDIN)) {
             setblock_stdin();
             if (interact && toplevel) {
-                int hstop = stophist;
+                int hstop = stophist, q;
                 stophist = 3;
                 /*
                  * Reset all errors including the interrupt error status
                  * immediately, so preprompt runs regardless of what
                  * just happened.  We'll reset again below as a
                  * precaution to ensure we get back to the command line
                  * no matter what.
                  */
+                q = queue_signal_level();
+                dont_queue_signals();
+                restore_queue_signals(q);
                 errflag = 0;
                 preprompt();
                 if (stophist != 3)
                     hbegin(1);
                 else
                     stophist = hstop;
                 /*
                  * Reset all errors, including user interrupts.
                  * This is what allows ^C in an interactive shell
                  * to return us to the command line.
                  */
                 errflag = 0;
             }


-- 
Mikael Magnusson




Messages sorted by: Reverse Date, Date, Thread, Author