Repository navigation
fix(timer): restore Ctrl+C cancellation while the timer runs - #642
Merged
Merged
Conversation
Ctrl+C no longer stopped a running timer; the process had to be killed
from another shell. The cause is upstream, in stdin-discarder@0.3.2 by
way of ora@9.4.1.
When the spinner starts, ora puts stdin in raw mode. Raw mode stops the
tty from turning Ctrl+C into SIGINT and delivers a bare 0x03 byte
instead. stdin-discarder means to read that byte and re-send the signal
itself, but it cannot: it attaches its reader with prependListener,
which (unlike on("data")) does not start a paused stream flowing, and it
only calls resume() when stdin.isPaused() was true beforehand. On a
fresh process.stdin, flowing is null, so isPaused() is false and the
resume never happens. The byte is never read, the signal is never
re-sent, and the tty's own SIGINT is disabled: Ctrl+C is swallowed for
the whole life of the spinner.
Turning discardStdin off leaves the terminal in canonical mode, so the
kernel delivers a real SIGINT again. Spinner already forwarded ora
options, so @node-cli/logger needed no change. There is nothing to bump
to instead: ora 9.4.1 and stdin-discarder 0.3.2 are both current.
Verified under a real pty, since ora only sets raw mode when isTTY:
released 2.1.1 is still alive 5s after Ctrl+C, while the patched build
exits in ~30ms with signal 2 when interrupted at 0.3s, 1.0s, 2.0s and
3.5s. The cursor is restored and the spinner line cleared on the way
out, and the expiry path still prints "Time's up!" and exits 0.
Tradeoff: keystrokes typed while the spinner runs are now echoed, which
is what discardStdin suppressed. That is ordinary CLI behavior and is
the lesser cost against a timer that cannot be cancelled; keeping the
discard is not possible without the raw mode that causes the bug.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5ssXq3kB2gv4wDANqjogQ
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Ctrl+C stopped cancelling a running timer — the process had to be killed from another shell.
Root cause
Upstream, in
stdin-discarder@0.3.2by way ofora@9.4.1— not in our code.When the spinner starts, ora puts stdin in raw mode. Raw mode stops the tty from turning Ctrl+C into SIGINT and delivers a bare
0x03byte instead.stdin-discardermeans to read that byte and re-send the signal itself, but it cannot:prependListener("data", …), which — unlikeon("data")— does not start a paused stream flowing;resume()whenstdin.isPaused()was true beforehand. On a freshprocess.stdin,flowingisnull, soisPaused()returns false and the resume never happens.The byte is never read, the signal is never re-sent, and the tty's own SIGINT is disabled. Ctrl+C is swallowed for the whole life of the spinner. Instrumentation shows it directly:
readableFlowing=nullwithdata listeners=1— a reader attached to a stream that never flows.Fix
One line, plus a comment so it doesn't get "cleaned up" later:
ora then leaves the terminal in canonical mode and the kernel delivers a real SIGINT.
Spinneralready forwarded ora options, so@node-cli/loggerneeded no change. There is nothing to bump to instead —ora@9.4.1andstdin-discarder@0.3.2are both current.Verification
Driven under a real pty, since ora only sets raw mode when
isTTY:signal=2ESC[?25l→ESC[?25h) and spinner line cleared on the way out✔ Time's up!, exit 0commentsreports unchangedTradeoff
Keystrokes typed while the spinner runs are now echoed — that is what
discardStdinsuppressed. Ordinary CLI behavior, and the lesser cost against a timer that cannot be cancelled. Keeping the discard is not possible without the raw mode that causes the bug.Note
3 pre-existing lint warnings in
src/__tests__/utilities.test.ts(unused imports) are untouched — separate PR if wanted.🤖 Generated with Claude Code
https://claude.ai/code/session_01T5ssXq3kB2gv4wDANqjogQ