Skip to content

fix(init): don't block on confirmation for 'init --here' without a TTY#3236

Open
jawwad-ali wants to merge 2 commits into
github:mainfrom
jawwad-ali:fix/init-here-noninteractive
Open

fix(init): don't block on confirmation for 'init --here' without a TTY#3236
jawwad-ali wants to merge 2 commits into
github:mainfrom
jawwad-ali:fix/init-here-noninteractive

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

Description

When specify init --here targets a non-empty directory and --force is not given, it called typer.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 --here path 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 --force fast-path are unchanged.

Testing

  • uvx ruff check clean.
  • New 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 --force guidance and leaves the pre-existing file untouched. Fails/blocks before, passes after.
  • The existing test_noninteractive_init_defaults_to_copilot still passes.

AI Disclosure

  • I did use AI assistance (describe below)

Found and fixed with Claude Code (Claude Opus 4.8) under my direction. AI spotted the unguarded typer.confirm on the --here path versus the already-guarded named-project path; I confirmed the _stdin_is_interactive helper's existing use, verified the fix and that scaffolding is skipped on abort, and reviewed the diff before submitting.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the init --here non-empty directory flow with _stdin_is_interactive() and exit with a clear --force message when non-interactive.
  • Add a CLI-level regression test covering init --here in 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

Comment thread src/specify_cli/commands/init.py Outdated
Comment on lines +232 to +241
elif not _stdin_is_interactive():
# No TTY to confirm on: fail fast with actionable guidance
# instead of blocking on typer.confirm (which would read EOF
# and abort unhelpfully). Mirrors the named-project path,
# which already errors and points to --force.
console.print(
"[red]Error:[/red] Current directory is not empty and no "
"interactive terminal is available to confirm. Re-run with "
"[bold]--force[/bold] to merge into it."
)
jawwad-ali and others added 2 commits June 29, 2026 23:30
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>
@jawwad-ali jawwad-ali force-pushed the fix/init-here-noninteractive branch from cd36c9b to b4a6fcd Compare June 29, 2026 18:35
@jawwad-ali

Copy link
Copy Markdown
Contributor Author

Fixed the failing pytest (ubuntu, 3.11) leg in b4a6fcd. The first revision guarded the confirmation on _stdin_is_interactive() (isatty), which short-circuited before typer.confirm and wrongly rejected piped confirmation (e.g. echo y | specify init --here), regressing test_init_here_without_force_preserves_shared_infra.

The revised fix calls typer.confirm normally — piped y/n is honored — and catches the Abort/EOFError it raises only when stdin is empty, converting that to the actionable --force guidance. Both test_init_here_without_force_preserves_shared_infra and the new no-input test pass locally; ruff clean.

AI disclosure: prepared with Claude Code (Claude Opus 4.8) under my direction; I reviewed the diff before pushing.

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.

3 participants