fix: GAP-020 - bound the first-run confirm prompt to 3 attempts - #264
Draft
Brian Krabach (bkrabach) wants to merge 1 commit into
Draft
fix: GAP-020 - bound the first-run confirm prompt to 3 attempts#264Brian Krabach (bkrabach) wants to merge 1 commit into
Brian Krabach (bkrabach) wants to merge 1 commit into
Conversation
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>
This was referenced Aug 13, 2026
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.
GAP-020: bound the first-run confirm prompt
rich.prompt.Confirm.ask()runs a barewhile True:internally with noattempt 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 onConfirm's owninput/validation primitives (
get_input/process_response/on_validate_error) that caps retries atmax_attempts(default 3).On the third invalid response it prints
No valid y/n response after 3 attempts. Skipping setup.and returnsFalse— 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 herewould 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 exactlymax_attemptsinvalid 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 unboundedwhile True:(pre-fixConfirm.ask()shape) makes it fail immediately:Restored →
5 passed. Full suite:1306 passed, 1 skipped, 13 deselected, 1 xfailed.ruff checkclean 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_confirmand its call site only.