Skip to content

fix(wizard): refuse to run without a terminal instead of crashing - #73

Merged
itelo merged 1 commit into
mainfrom
itelo/wizard-requires-tty
Sep 3, 2026
Merged

itelo merged 1 commit into
mainfrom
itelo/wizard-requires-tty

Conversation

@itelo

@itelo itelo commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Found while verifying analytics end to end: running the wizard with stdin redirected prints a React stack trace out of Ink's internals instead of anything a developer can act on.

The failure

$ seam wizard < /dev/null

  ERROR Raw mode is not supported on the current process.stdin, which Ink uses
       as input stream by default.
     at commitHookPassiveMountEffects (react-reconciler.development.js:10948:11)
     at commitPassiveMountOnFiber (react-reconciler.development.js:12979:13)
     …

The welcome splash already handles a missing TTY — it skips straight to init — but ink-select-input calls useInput unconditionally, and Ink's setRawMode throws when stdin is not a TTY (ink/build/components/App.js:211). So the run died mid-render, on the first prompt, after doing real work: in my repro it had already verified the key, connected the workspace, and started installing the SDK before it blew up.

The fix

There is no headless path through the wizard worth preserving: every run reaches a prompt — the connect method, the SDK, the offer to write the integration — so a non-interactive run can never finish, whatever we do at the prompt. Check for a TTY at the entrypoint and say what's wrong:

Seam Wizard needs an interactive terminal.

  It asks which account to connect, which SDK to install, and whether to
  write the integration, so it cannot run with stdin redirected — in a
  pipe, a CI job, or a non-interactive shell.

  Run 'seam wizard' in your terminal instead.

To stderr, with exit code 1, and no render at all. The message uses the host's commandName, so mounted under the Seam CLI it says seam wizard.

Two deliberate boundaries:

  • The check sits after --help and --version, which are the documented ways to run this without a terminal and keep working (covered by tests).
  • Only stdin is required. seam wizard | tee log still runs: render.ts already skips the alternate screen when stdout is redirected and leaves its frames in the log.

Testing

  • test/wizard.test.ts — the refusal (message, no render, exit code), and --help / --version still working without a TTY. The suite now makes stdin look like a terminal in beforeEach, since that is the normal case for the eight existing tests that expect a render; the refusal tests opt out. process.exitCode is reset in afterEach so the refusal doesn't fail the run.
  • Ran it for real, both ways: piped gives the notice above and exit 1 with no stack trace; under a pty (script -q /dev/null npm run wizard) the guard doesn't fire and the wizard renders as before.

npm run typecheck, npm run lint and npm test (197 tests) pass.

🤖 Generated with Claude Code

@itelo
itelo requested a review from razor-x as a code owner September 3, 2026 17:18
Piping the wizard — a CI job, `seam wizard < /dev/null`, any
non-interactive shell — printed a React stack trace out of Ink's
internals:

    ERROR Raw mode is not supported on the current process.stdin, which
    Ink uses as input stream by default.
        at commitHookPassiveMountEffects (react-reconciler.development.js)
        …

The welcome splash already handles a missing TTY (it skips straight to
init), but `ink-select-input` calls `useInput` unconditionally, and Ink's
`setRawMode` throws when stdin is not a TTY. So the run died on the first
prompt it rendered, mid-render, after having done real work: it had
already connected the workspace and started installing the SDK.

Nothing can be done about that at the prompt, because every run reaches
one — the connect method, the SDK, the offer to write the integration —
so there is no headless path through the wizard to preserve. Check for a
TTY at the entrypoint instead and explain what is wrong:

    Seam Wizard needs an interactive terminal.

      It asks which account to connect, which SDK to install, and whether
      to write the integration, so it cannot run with stdin redirected —
      in a pipe, a CI job, or a non-interactive shell.

      Run 'seam wizard' in your terminal instead.

To stderr, with a non-zero exit code, and no render at all. The check
sits after `--help` and `--version`, which are the documented ways to run
this without a terminal and keep working.

Only stdin is required: a run with stdout redirected still works, since
render.ts already skips the alternate screen there and leaves its frames
in the log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@itelo
itelo force-pushed the itelo/wizard-requires-tty branch from 3970038 to ad802ed Compare September 3, 2026 17:28
@itelo
itelo merged commit 1639977 into main Sep 3, 2026
11 checks passed
@itelo
itelo deleted the itelo/wizard-requires-tty branch September 3, 2026 17:30
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