Skip to content

fix(init): fix InkUI on native Windows terminals (CLI-1NT)#978

Draft
betegon wants to merge 4 commits into
mainfrom
fix/init-windows-inkui-tty
Draft

fix(init): fix InkUI on native Windows terminals (CLI-1NT)#978
betegon wants to merge 4 commits into
mainfrom
fix/init-windows-inkui-tty

Conversation

@betegon
Copy link
Copy Markdown
Member

@betegon betegon commented May 19, 2026

Summary

sentry init was failing on Windows in CMD.exe, PowerShell, and Windows Terminal with "The interactive UI failed to load" since 0.33.0 (when InkUI replaced OpenTUI). Git Bash / MSYS2 users were fine because their POSIX layer maps /dev/tty — native Windows terminals don't have it.

The failure chain: openFreshTtyForInk() returns null on Windows → Ink falls back to process.stdin (Bun's broken fd 0, oven-sh/bun#6862) → mountApp() throws → getUIAsync() swallows it silently → LoggingUI is used → LoggingUIPromptError in preamble()WizardError.

Changes

ink-ui.ts — try \\.\CON as the TTY device path on Windows. It's the console device equivalent of /dev/tty: a freshly-opened handle is not fd 0 and should deliver readable events correctly, bypassing the Bun bug. A setRawMode(true/false) probe runs immediately after opening to verify the handle actually supports raw mode — if it throws, the function returns null and falls back gracefully rather than blowing up inside mountApp.

factory.ts — add a breadcrumb (with stack) in the getUIAsync catch block. Previously the actual InkUI throw was swallowed with no trace; only the downstream LoggingUIPromptError appeared in Sentry. The breadcrumb will confirm whether \\.\CON fixes the issue or if there's a secondary failure to address.

wizard-runner.ts — when LoggingUIPromptError fires but stdin.isTTY is true, suggest --no-tui --yes instead of just --yes. The old message was misleading for any user who expected interactive mode.

Test Plan

  • Run sentry init from CMD.exe or PowerShell on Windows 10/11 with Bun — should enter the wizard instead of immediately erroring
  • Git Bash on Windows should be unaffected (already worked)
  • After shipping, new CLI-1NT events should carry a breadcrumb showing the real InkUI error if \\.\CON doesn't fully resolve it on some setups

Fixes CLI-1NT

On Windows, /dev/tty doesn't exist in CMD.exe, PowerShell, or Windows
Terminal. openFreshTtyForInk() returned null, leaving Ink reading from
process.stdin — the broken Bun fd 0 (oven-sh/bun#6862) — which caused
mountApp() to throw. getUIAsync() swallowed the throw silently and fell
back to LoggingUI, which then threw LoggingUIPromptError in preamble(),
surfacing as "The interactive UI failed to load."

Git Bash / MSYS2 / Cygwin users were unaffected because their POSIX
emulation layer maps /dev/tty correctly.

Three changes:

- ink-ui.ts: try \\.\CON as the TTY device path on Windows. A freshly
  opened console handle is not fd 0 and should deliver readable events
  correctly, the same way /dev/tty does on Unix. A setRawMode(true/false)
  probe verifies the handle supports raw mode before handing it to Ink —
  if it fails the function returns null and falls back gracefully.

- factory.ts: add a breadcrumb in the InkUI catch block (including the
  error stack) so the actual throw is visible in Sentry. Previously only
  the downstream LoggingUIPromptError appeared, with no indication of
  the real cause.

- wizard-runner.ts: when LoggingUIPromptError fires but isTTY is true,
  point the user to --no-tui --yes rather than just --yes, which alone
  doesn't explain why the TUI failed.

Fixes CLI-1NT
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-978/

Built to branch gh-pages at 2026-05-19 12:30 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Comment thread src/lib/init/ui/ink-ui.ts
betegon added 2 commits May 19, 2026 12:45
…fd leak

If openSync and new ReadStream both succeed but setRawMode(true) throws
(e.g. a \.\CON handle that passes open but fails SetConsoleMode), the
catch block was returning null without closing the stream, leaking the
underlying file descriptor.

Hoist stream outside the try block so the catch can call stream?.destroy()
before returning null. The optional chain is a no-op when the error
occurred in openSync or the ReadStream constructor.

Reported by sentry-warden on PR #978.
The error message now branches on process.stdin.isTTY: interactive TTY
gets '--no-tui --yes' guidance; non-TTY gets '--yes'. Update the test
name and expected string to match the isTTY=true branch that forceStdinTty
exercises. The non-TTY branch is unreachable via LoggingUIPromptError
(preamble throws earlier with a different message).
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

Codecov Results 📊

6990 passed | Total: 6990 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +4
Passed Tests 📈 +4
Failed Tests
Skipped Tests

All tests are passing successfully.

❌ Patch coverage is 75.00%. Project has 14140 uncovered lines.
✅ Project coverage is 77.12%. Comparing base (base) to head (head).

Files with missing lines (2)
File Patch % Lines
src/lib/init/ui/factory.ts 78.57% ⚠️ 3 Missing
src/lib/init/wizard-runner.ts 66.67% ⚠️ 2 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    77.08%    77.12%    +0.04%
==========================================
  Files          320       320         —
  Lines        61811     61802        -9
  Branches         0         0         —
==========================================
+ Hits         47646     47662       +16
- Misses       14165     14140       -25
- Partials         0         0         —

Generated by Codecov Action

…Error hint

factory.mocked.test.ts: 4 tests covering the getUIAsync() catch path —
fallback to LoggingUI when createInkUI throws an Error, rejects with a
non-Error, or when the dynamic import itself fails. Also verifies the
fallback is stateless across consecutive calls. mock.module() isolates
the ink-ui sidecar so the Ink renderer is never spun up in tests.

wizard-runner.test.ts: update test name and expected message to match
the isTTY=true branch that forceStdinTty() exercises (--no-tui --yes
guidance instead of the old --yes-only hint). Remove the trailing blank
line left by the deleted non-TTY test case.
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