Skip to content

feat: fail fast when init has no terminal to prompt on - #158

Merged
Bccorb merged 1 commit into
mainfrom
feat/init-requires-tty-main
Aug 1, 2026
Merged

feat: fail fast when init has no terminal to prompt on#158
Bccorb merged 1 commit into
mainfrom
feat/init-requires-tty-main

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Re-lands #157 against main. Reopens #153. Part of #154.

#157 was stacked on feat/init-non-interactive, and that base branch was merged into main (#156) before #157 merged into it. GitHub marked #157 MERGED because its own base received the commit, but main never did: src/core/tty.ts is not on main and init still hangs on a pipe there.

This is 210de45 cherry-picked onto current main. 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:

$ seamless init --local < /dev/null
Error: "Web example" needs an interactive terminal, and this run does not have one.
Pass --web=<id> to choose one (see `seamless templates list`), or --yes to take the
recommended template.

An implausible terminal width warns rather than failing, since a narrow pty still accepts input.

Checks

npm run build and npm test pass on top of main (781 passed, 4 skipped). Re-ran the smoke test against the rebuilt CLI: the piped run fails fast, and a fully flagged --yes run still scaffolds through a pipe.

#153 stays open after this for the commands outside init that still hang on a prompt (login, profile, config apply, users delete, sessions revoke).

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
@Bccorb
Bccorb merged commit e49745c into main Aug 1, 2026
2 checks passed
@Bccorb
Bccorb deleted the feat/init-requires-tty-main branch August 1, 2026 04:18
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
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.

1 participant