Skip to content

Commit b4a6fcd

Browse files
jawwad-aliclaude
andcommitted
fix(init): honor piped confirmation for 'init --here'; only fail-fast 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>
1 parent 079c9a8 commit b4a6fcd

2 files changed

Lines changed: 20 additions & 20 deletions

File tree

src/specify_cli/commands/init.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -229,19 +229,21 @@ def init(
229229
console.print(
230230
"[cyan]--force supplied: skipping confirmation and proceeding with merge[/cyan]"
231231
)
232-
elif not _stdin_is_interactive():
233-
# No TTY to confirm on: fail fast with actionable guidance
234-
# instead of blocking on typer.confirm (which would read EOF
235-
# and abort unhelpfully). Mirrors the named-project path,
236-
# which already errors and points to --force.
237-
console.print(
238-
"[red]Error:[/red] Current directory is not empty and no "
239-
"interactive terminal is available to confirm. Re-run with "
240-
"[bold]--force[/bold] to merge into it."
241-
)
242-
raise typer.Exit(1)
243232
else:
244-
response = typer.confirm("Do you want to continue?")
233+
try:
234+
response = typer.confirm("Do you want to continue?")
235+
except (typer.Abort, EOFError):
236+
# No confirmation input available (non-interactive session
237+
# with empty stdin): fail fast with actionable guidance
238+
# instead of the bare "Aborted." Piped input (e.g. "y") is
239+
# still honored above. Mirrors the named-project path,
240+
# which already points to --force.
241+
console.print(
242+
"[red]Error:[/red] Current directory is not empty and no "
243+
"confirmation input is available. Re-run with "
244+
"[bold]--force[/bold] to merge into it."
245+
)
246+
raise typer.Exit(1) from None
245247
if not response:
246248
console.print("[yellow]Operation cancelled[/yellow]")
247249
raise typer.Exit(0)

tests/integrations/test_cli.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,14 @@ def fail_select(*_args, **_kwargs):
121121
data = json.loads((project / ".specify" / "integration.json").read_text(encoding="utf-8"))
122122
assert data["integration"] == specify_cli.DEFAULT_INIT_INTEGRATION
123123

124-
def test_init_here_nonempty_noninteractive_errors_with_force_guidance(self, tmp_path, monkeypatch):
125-
"""`init --here` on a non-empty directory must not block on a confirmation
126-
prompt when there is no interactive terminal: it should fail fast with
127-
guidance to use --force, instead of reading EOF and aborting unhelpfully."""
124+
def test_init_here_nonempty_noninteractive_errors_with_force_guidance(self, tmp_path):
125+
"""`init --here` on a non-empty directory with no confirmation input (empty
126+
stdin) must fail fast with guidance to use --force, instead of the bare
127+
'Aborted.' from an EOF on typer.confirm. CliRunner with no `input=` provides
128+
empty stdin, so typer.confirm raises Abort, which the command converts to the
129+
actionable error."""
128130
from typer.testing import CliRunner
129131
from specify_cli import app
130-
from specify_cli.commands import init as init_mod
131-
132-
# Deterministically exercise the non-interactive branch.
133-
monkeypatch.setattr(init_mod, "_stdin_is_interactive", lambda: False)
134132

135133
project = tmp_path / "nonempty-here"
136134
project.mkdir()

0 commit comments

Comments
 (0)