Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Unreleased

- Handle a second `KeyboardInterrupt` while printing `Aborted!` after
`prompt()` is cancelled with Ctrl-C. Previously that interrupt could
escape `Command.main` as an unhandled exception. {issue}`3802`

## Version 8.5.0

Released 2026-08-24
Expand Down
10 changes: 8 additions & 2 deletions src/click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1561,7 +1561,10 @@ def main(
# by its truthiness/falsiness
ctx.exit()
except (EOFError, KeyboardInterrupt) as e:
echo(file=sys.stderr)
try:
echo(file=sys.stderr)
except KeyboardInterrupt:
pass
raise Abort() from e
except ClickException as e:
if not standalone_mode:
Expand Down Expand Up @@ -1591,7 +1594,10 @@ def main(
except Abort:
if not standalone_mode:
raise
echo(_("Aborted!"), file=sys.stderr)
try:
echo(_("Aborted!"), file=sys.stderr)
except KeyboardInterrupt:
pass
sys.exit(1)

def _main_shell_completion(
Expand Down
32 changes: 32 additions & 0 deletions tests/test_utils/test_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,38 @@ def capture(text):
assert received == [expected_prompt]


def test_prompt_abort_echo_survives_keyboard_interrupt(monkeypatch):
"""Ctrl-C at a prompt becomes Abort. A second KeyboardInterrupt
during the Aborted! echo (echo -> isatty) must still exit 1.

https://github.com/pallets/click/issues/3802
"""

@click.command()
def cli():
click.prompt("name")

def interrupt(_text):
raise KeyboardInterrupt()

monkeypatch.setattr("click.termui.visible_prompt_func", interrupt)

stderr = StringIO()

def interrupt_isatty():
raise KeyboardInterrupt()

stderr.isatty = interrupt_isatty # type: ignore[method-assign]
monkeypatch.setattr(sys, "stderr", stderr)

try:
cli.main([])
except SystemExit as e:
assert e.code == 1
except KeyboardInterrupt:
pytest.fail("KeyboardInterrupt escaped Command.main during abort")


def test_prompts_eof(runner):
"""If too few lines of input are given, prompt should exit, not hang."""

Expand Down