feat: fail fast when init has no terminal to prompt on - #158
Merged
Conversation
Run on a pipe, init used to render a @Clack prompt nobody could answer and wait forever, so a CI step failed only when its job timed out. Every prompt in the init flow is now fronted by requireInteractive, which stops on the first unanswered question and names the flag that answers it. A run whose answers all come from flags is unaffected and behaves the same on a pipe as on a terminal. A terminal too narrow to render a prompt (a pty allocated without a size reports one column, which printed one character per line) now warns rather than just looking broken. Scoped to init. The other commands that prompt are still unguarded, tracked in the issue. Closes #153
7 tasks
This was referenced Aug 1, 2026
Bccorb
added a commit
that referenced
this pull request
Aug 1, 2026
seamless init stopped rendering prompts to a pipe in #158; every other command still hung there until its job timed out. Every prompt is now fronted by requireInteractive, so a run without a TTY on stdin fails naming the flag that answers the question: login, profile add, users delete, users prepare-device-replacement, sessions revoke, org members remove, config apply, and config oauth-providers remove. A guard with nothing to point at would only convert a hang into a dead end, so the destructive confirmations now take --force, matching what init means by it. They share confirmDestructive, which answers itself when forced and otherwise asks. hasForceFlag also accepts --yes and -y, because config oauth-providers remove --yes shipped before the convention existed. --force does not override --dry-run, and cancelling still reads as declining rather than as an error. The one-time code at login has no flag form (it does not exist until the code is sent), so that step reports that it genuinely needs a terminal. Tests get a setup file that marks stdin a TTY, since the guard would otherwise fail every test that exercises a prompt; the no-terminal tests opt back out. Closes #159
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.
Re-lands #157 against
main. Reopens #153. Part of #154.#157 was stacked on
feat/init-non-interactive, and that base branch was merged intomain(#156) before #157 merged into it. GitHub marked #157 MERGED because its own base received the commit, butmainnever did:src/core/tty.tsis not onmainandinitstill hangs on a pipe there.This is
210de45cherry-picked onto currentmain. It applied cleanly with no conflicts, and the content is identical to what was already reviewed on #157.What it does
Every prompt in the init flow is fronted by
requireInteractive(src/core/tty.ts), so a run without a TTY on stdin stops on the first unanswered question and names the flag that answers it, instead of rendering a prompt nobody can answer and waiting forever:An implausible terminal width warns rather than failing, since a narrow pty still accepts input.
Checks
npm run buildandnpm testpass on top ofmain(781 passed, 4 skipped). Re-ran the smoke test against the rebuilt CLI: the piped run fails fast, and a fully flagged--yesrun still scaffolds through a pipe.#153stays open after this for the commands outsideinitthat still hang on a prompt (login,profile,config apply,users delete,sessions revoke).