Investigated this further. The handler isn't exactly late. It is called before errflag checks, but exits here without setting errflag: /* Are we queueing signals now? */ if (queueing_enabled) { The log looks now like this: -- tmoutp != ZTM_NONE / raw_getbyte() zle_main.c ## zhandler(2) signals.c (debug message in first line of zhandler) ## zhandler(2) EXIT 1 signals.c (debug message in if(queueing_enabled)) -- NOT doing break selret[-1/4/4] / zle_main.c: errflag: 0, retflag: 0, breaks: 0, exit_pending: 0 -- Trying again selret[-1], !errtry[0] / zle_main.c -- tmoutp != ZTM_NONE / raw_getbyte() zle_main.c -- (**) NOT doing break selret[0/4/4] / zle_main.c: errflag: 0, retflag: 0, breaks: 0, exit_pending: 0 -- Passed !errtry(errtry:1) selret[0] / zle_main.c ## zhandler(2) signals.c ## set errflag to 2 (ERRFLAG_INT:2) / signals.c -- tmoutp->tp <- ZTM_NONE / calc_timeout( do_keytmout: 0 ), keytimeout: 40 / zle_main.c == CALC_TIMEOUT() one more chance (timedfns exp100ths: 100) -- CALC_TIMEOUT() tfnode TRUE no break NOT calling >> DIFF=1 << -- CALC_TIMEOUT() 0 != 1 || 100 <= 100 ZTM_FUNC(2) ^^^ LOOP-CALLED calc_timeout: tmout.tp == 2 / zle_main.c Interesting is (**) line, it still has errno == 4 (last number in [%d/%d/%d]), apparently errno=0 is needed before select, doing so clears the value. What's with the queueing and early exit from zhandler()? It brings disorder to .recursiveedit. Best regards, Sebastian Gniazdowski