Skip to content

fix(init): stop specify init hanging on arrow-key pickers in agent harnesses - #4178

Merged
mnriem merged 2 commits into
github:mainfrom
lllakshit:fix/4152-noninteractive-init-hang
Aug 18, 2026
Merged

fix(init): stop specify init hanging on arrow-key pickers in agent harnesses#4178
mnriem merged 2 commits into
github:mainfrom
lllakshit:fix/4152-noninteractive-init-hang

Conversation

@lllakshit

@lllakshit lllakshit commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #4152.

specify init can hang forever in select_with_arrows waiting for arrow-key input. --force only skips the merge confirmation, so an invocation like specify init --here --integration claude --force still reaches the script-type picker when stdin looks interactive.

That happens in AI agent harnesses: they often allocate a PTY (isatty() is true) but cannot send keypresses. The process then blocks with no timeout and no useful error.

This change:

  1. Fails fast inside select_with_arrows when stdin is not a TTY, naming the flag that would supply the choice (--integration / --script) instead of hanging.
  2. Adds --non-interactive so callers can suppress every picker even when stdin is a TTY. Unspecified selections use documented defaults (integration via SPECKIT_INTEGRATION_DEFAULT / Copilot; script type ps on Windows, sh otherwise). Merge into a non-empty directory still requires --force — there is no safe default for overwrite.
  3. URL --extension installs follow the same rule: --non-interactive never calls typer.confirm. Without --trust-extension-urls the URL is denied; with it, the install proceeds without a prompt.

--force is unchanged: it still only means merge/overwrite, not "answer every prompt."

Workflows that already run multi-step processes non-interactively are not affected. This is specifically the specify init arrow-key picker path.

Testing

  • Tested locally with specify init --help (--non-interactive is listed)
  • Ran existing tests (focused pytest covering this change)
  • Tested with a sample project

Focused pytest:

  • picker / --non-interactive cases
  • HTTPS --extension with TTY + --non-interactive (deny without --trust-extension-urls; install with it; typer.confirm fails if called)
  • interactive Claude/Alquimia init tests

Local smoke test:

specify init <temp-dir> --non-interactive --ignore-agent-tools

completed without prompting, defaulted to Copilot / ps on Windows, and wrote .specify/init-options.json.

Test selection reasoning

Changed file Affects Test Why
src/specify_cli/_console.py specify init pickers T1 Fail-fast lives in the shared selector
src/specify_cli/commands/init.py specify init T1 --non-interactive flag, prompt gating, URL allow_prompt
docs / README docs only User-facing flag documentation

Required tests

  • T1: specify init --non-interactive — CLI init/scaffolding change; /speckit.specify is not affected (no command templates or scripts changed)

Manual test results

OS/Shell: Windows 10 / PowerShell

Command tested Notes
specify init --help --non-interactive listed
specify init <temp> --non-interactive --ignore-agent-tools Pass — no hang, defaults to copilot + ps
pytest (non-interactive + URL extension cases) Pass

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

AI assistance was used to explore the TTY vs PTY hang and to draft tests. I reviewed the existing init prompt paths, chose fail-fast plus an explicit --non-interactive flag (without overloading --force), and ran the tests above.

@lllakshit
lllakshit requested a review from mnriem as a code owner August 17, 2026 19:26
…rnesses

Agent harnesses often allocate a PTY so isatty is true, but they cannot send
arrow keys. Fail fast when stdin is not a TTY, and add --non-interactive so
scripted init applies defaults instead of hanging.

Fixes github#4152.
@lllakshit
lllakshit force-pushed the fix/4152-noninteractive-init-hang branch from d32f3cb to 8c57587 Compare August 17, 2026 19:32
@mnriem
mnriem requested a balanced review from Copilot August 17, 2026 20:21

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

Prevents specify init from hanging on arrow-key prompts in automated environments.

Changes:

  • Adds non-TTY fail-fast behavior to the shared selector.
  • Adds --non-interactive with safe defaults and prompt suppression.
  • Adds tests and user documentation.
Show a summary per file
File Description
src/specify_cli/_console.py Adds non-TTY detection and actionable errors.
src/specify_cli/commands/init.py Implements non-interactive initialization.
tests/test_console_imports.py Tests selector fail-fast behavior.
tests/test_live_transient_windows.py Adapts selector tests for TTY detection.
tests/integrations/test_cli.py Tests non-interactive initialization scenarios.
README.md Documents automation usage.
docs/quickstart.md Explains non-interactive defaults.
docs/local-development.md Clarifies script selection behavior.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/specify_cli/commands/init.py

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please address Copilot feedback

…trust

Cover the HTTPS --extension confirmation path when stdin is a TTY: deny
without --trust-extension-urls, and install with it, both without calling
typer.confirm.
@lllakshit

Copy link
Copy Markdown
Contributor Author

Addressed the requested Copilot feedback in 1cef89a.

Added tests that run specify init --non-interactive with an HTTPS --extension while _stdin_is_interactive() is true, and fail if typer.confirm is called:

  • without --trust-extension-urls, the URL is denied and typer.confirm is never invoked
  • with --trust-extension-urls, install proceeds and typer.confirm is never invoked

Also covered the nonempty --here confirm path under --non-interactive the same way.

Please re-review.

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.

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@mnriem
mnriem self-requested a review August 18, 2026 13:16
@mnriem
mnriem merged commit a5c3ba4 into github:main Aug 18, 2026
14 checks passed
@mnriem

mnriem commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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.

specify init hangs on select_with_arrows prompts in non-interactive environments — no flag reaches them, --force does not bypass them

3 participants