Skip to content

fix: GAP-020 - bound the first-run confirm prompt to 3 attempts - #264

Draft
Brian Krabach (bkrabach) wants to merge 1 commit into
mainfrom
fix/gap-020-bounded-confirm
Draft

fix: GAP-020 - bound the first-run confirm prompt to 3 attempts#264
Brian Krabach (bkrabach) wants to merge 1 commit into
mainfrom
fix/gap-020-bounded-confirm

Conversation

@bkrabach

Copy link
Copy Markdown
Collaborator

GAP-020: bound the first-run confirm prompt

rich.prompt.Confirm.ask() runs a bare while True: internally with no
attempt limit — any non-y/n response just re-prompts, forever. That loop
sits at the first-run "Run setup now?" gate a brand-new user hits before
anything else works, so a stray keypress, a pasted line, or an automation
sending the wrong thing leaves the process stuck with no signal it will
ever end.

The fix

_bounded_confirm() is a drop-in replacement built on Confirm's own
input/validation primitives (get_input / process_response /
on_validate_error) that caps retries at max_attempts (default 3).

On the third invalid response it prints No valid y/n response after 3 attempts. Skipping setup. and returns False — the same fall-through the
"setup skipped" path already takes for an explicit n.

EOF/Ctrl-C are deliberately left untouched: Click's BaseCommand.main()
already converts those to a clean Aborted! exit, and catching them here
would silently turn "the user asked to stop" into "skip setup and keep
going."

Test evidence

tests/test_gap020_bounded_confirm.py (5 tests): terminates after exactly
max_attempts invalid responses; honours a configurable limit (1, 2, 5);
a valid answer short-circuits on the first attempt without consuming the
retry budget; one invalid answer followed by a valid one recovers
correctly; bare Enter returns the default.

Teeth proof — reverting _bounded_confirm's loop to the old unbounded
while True: (pre-fix Confirm.ask() shape) makes it fail immediately:

FAILED tests/test_gap020_bounded_confirm.py::test_invalid_input_terminates_after_max_attempts
FAILED tests/test_gap020_bounded_confirm.py::test_max_attempts_is_actually_honoured
AssertionError: _bounded_confirm asked for input 51 times -- the loop is unbounded. This is the GAP-020 hang.

Restored → 5 passed. Full suite: 1306 passed, 1 skipped, 13 deselected, 1 xfailed.
ruff check clean on both changed files.

Context

Extracted from #259, which bundles this fix together with four unrelated
stories across 15 commits — a peer reviewer flagged the context-switching
cost. Following the same extraction approach as #263 (GAP-003), this
branch is scoped to _bounded_confirm and its call site only.

rich.prompt.Confirm.ask() runs a bare `while True:` internally with no
attempt limit: any non-y/n response just re-prompts, forever. That loop
sits at the first-run "Run setup now?" gate a brand-new user hits before
anything else works, so a stray keypress, a pasted line, or an automation
sending the wrong thing leaves the process stuck with no signal it will
ever end.

Adds _bounded_confirm(), a drop-in replacement built on Confirm's own
input/validation primitives (get_input / process_response /
on_validate_error) that caps retries at max_attempts (default 3). On the
third invalid response it prints "No valid y/n response after 3 attempts.
Skipping setup." and returns False -- the same fall-through the "setup
skipped" path already takes for an explicit "n". EOF/Ctrl-C are
deliberately left untouched: Click's BaseCommand.main() already converts
those to a clean "Aborted!" exit, and catching them here would silently
turn "the user asked to stop" into "skip setup and keep going."

Test evidence (tests/test_gap020_bounded_confirm.py, 5 tests): terminates
after exactly max_attempts invalid responses; honours a configurable limit
(1, 2, 5); a valid answer short-circuits on the first attempt without
consuming the retry budget; one invalid answer followed by a valid one
recovers correctly; bare Enter returns the default. Reverting
_bounded_confirm() to the old unbounded Confirm.ask() call makes the
first test fail with "_bounded_confirm asked for input 51 times -- the
loop is unbounded. This is the GAP-020 hang." (see PR description for the
before/after run).

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants