fix(init): don't block on confirmation for 'init --here' without a TTY#3236
fix(init): don't block on confirmation for 'init --here' without a TTY#3236jawwad-ali wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes specify init --here behavior in non-interactive sessions by avoiding an unconditional confirmation prompt when the current directory is non-empty (and --force is not provided). It aligns the --here path with the existing named-project path by failing fast with actionable --force guidance instead of blocking/EOF-aborting.
Changes:
- Guard
typer.confirm(...)in theinit --herenon-empty directory flow with_stdin_is_interactive()and exit with a clear--forcemessage when non-interactive. - Add a CLI-level regression test covering
init --herein a non-empty directory under a forced non-interactive stdin.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/commands/init.py |
Adds a non-interactive guard to prevent blocking/EOF behavior and provide --force guidance for init --here into non-empty dirs. |
tests/integrations/test_cli.py |
Adds a regression test ensuring non-interactive init --here into a non-empty directory exits 1 with --force guidance and preserves existing files. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Low
cd36c9b to
b4a6fcd
Compare
|
Fixed the failing The revised fix calls AI disclosure: prepared with Claude Code (Claude Opus 4.8) under my direction; I reviewed the diff before pushing. |
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback. If not applicable, please explain why
When 'specify init --here' targets a non-empty directory without --force, it called typer.confirm() unconditionally. In a non-interactive session (no TTY -- CI, piped, agent) there is no input, so the prompt reads EOF and aborts unhelpfully (or blocks), with no actionable message. The named-project path already fails fast and points to --force; --here was the inconsistent outlier. Guard the confirmation with the existing _stdin_is_interactive() helper: when non-interactive, print a clear 'directory not empty; re-run with --force' error and exit 1 instead of prompting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… on empty stdin The first version of this fix short-circuited on '_stdin_is_interactive()' (isatty) before typer.confirm, which broke 'init --here' when confirmation is piped (e.g. 'echo y | specify init --here' / CliRunner input='y\n') -- a non-TTY pipe with valid input was wrongly rejected, regressing test_init_here_without_force_preserves_shared_infra. Instead, call typer.confirm normally (piped 'y'/'n' is honored) and catch the Abort/EOFError it raises only when stdin is empty, converting that to the actionable '--force' guidance. This keeps the UX win for the no-input case without rejecting piped input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…warning Address Copilot review on the --here non-empty path: (1) treat typer.Abort during an interactive confirm (e.g. Ctrl+C) as a normal cancellation (exit 0), and only emit the '--force' guidance + exit 1 when there is no TTY (empty stdin / EOF) -- no longer conflating the two; (2) move the 'will be merged / may overwrite' warning so it only shows when actually proceeding (force) or folded into the confirmation prompt, not on the fail-fast path where nothing is merged. Piped confirmation (e.g. 'echo y | specify init --here') is still honored, which is why the prompt is attempted rather than refused outright when non-interactive -- the existing test_init_here_without_force_preserves_shared_infra pipes 'y' and must succeed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b4a6fcd to
dacd64b
Compare
|
Please address Copilot feedback. If not applicable, please explain why |
Per Copilot review: do not call typer.confirm when stdin is not a TTY -- an open-but-idle non-TTY stdin (CI/agent) could block on the prompt. When the directory is non-empty and --force is not given, fail fast with '--force' guidance unless an interactive terminal is present. Interactive confirm still offers the merge-but-preserve path (distinct from --force, which overwrites); a Ctrl+C there is treated as a normal cancellation (exit 0). The merge/overwrite warning is only printed when actually proceeding, not on the fail-fast path. Updated the preserve-merge E2E test to simulate an interactive terminal so it exercises the confirm path (non-interactive sessions now require --force). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@mnriem addressed Copilot's feedback in the latest commit: |
|
Requesting changes — this introduces a breaking change to a legitimate, common usage. The current final commit guards the confirmation with Critically,
So a script doing Your second revision ( else:
console.print(
"[yellow]Template files will be merged with existing content and may overwrite existing files[/yellow]"
)
try:
proceed = typer.confirm("Do you want to continue?")
except (typer.Abort, EOFError):
console.print(
"[red]Error:[/red] Current directory is not empty and no confirmation "
"input is available. Re-run with [bold]--force[/bold] to merge into it."
)
raise typer.Exit(1) from None
if not proceed:
console.print("[yellow]Operation cancelled[/yellow]")
raise typer.Exit(0)This keeps all cases working: piped The only uncovered case is an open-but-idle non-TTY pipe (the original blocking bug). Breaking the common |
Per maintainer review: restore the second-revision shape. Calling typer.confirm normally keeps 'echo y | specify init --here' reaching the non-destructive preserve-merge path (and piped 'n' cancels with exit 0). Only when no confirmation input is available at all (closed/empty stdin -> typer.Abort/EOFError) is it converted into the actionable error that points at --force. This drops the _stdin_is_interactive fail-fast that broke the common piped-confirm idiom and made preserve-merge interactive-only. The preserve test no longer needs to monkeypatch _stdin_is_interactive - it passes on the real contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks @mnriem — you're right, and I've restored the second-revision shape in
I dropped the (AI-assisted: implemented with Claude Code under my direction and review.) |
Description
When
specify init --heretargets a non-empty directory and--forceis not given, it calledtyper.confirm("Do you want to continue?")unconditionally. In a non-interactive session (CI, piped input, an agent driving the CLI) there is no TTY, so the prompt reads EOF and aborts unhelpfully (or blocks waiting for input) — with no actionable message.The named-project path already handles this correctly: it fails fast with an error panel pointing to
--force. The--herepath was the inconsistent outlier, even though the module already has a_stdin_is_interactive()helper used elsewhere.Fix
Guard the confirmation with
_stdin_is_interactive(): when non-interactive, print a clear "directory is not empty … re-run with --force to merge" error and exit 1 instead of prompting. Interactive behavior and the--forcefast-path are unchanged.Testing
uvx ruff checkclean.test_init_here_nonempty_noninteractive_errors_with_force_guidance: a non-empty cwd +init --here(no--force) under a non-interactive stdin now exits 1 with--forceguidance and leaves the pre-existing file untouched. Fails/blocks before, passes after.test_noninteractive_init_defaults_to_copilotstill passes.AI Disclosure
Found and fixed with Claude Code (Claude Opus 4.8) under my direction. AI spotted the unguarded
typer.confirmon the--herepath versus the already-guarded named-project path; I confirmed the_stdin_is_interactivehelper's existing use, verified the fix and that scaffolding is skipped on abort, and reviewed the diff before submitting.