Skip to content

init: raw ANSI escape sequences in output when piped or captured non-interactively #746

@betegon

Description

@betegon

Problem

When sentry init --yes output is piped or redirected (e.g., sentry init --yes | tee log.txt, or captured in CI), the output contains raw terminal control sequences that make it unreadable without heavy filtering.

The captured output includes:

  • Cursor hide/show sequences (\x1b[?25l, \x1b[?25h)
  • Cursor movement codes (\x1b[999D, \x1b[J)
  • Spinner animation frames (braille characters redrawn in-place)
  • ANSI color/SGR codes from picocolors
  • Unicode box-drawing characters from clack's UI chrome

Example of what a CI log or pipe capture looks like:

[?25l◆  EXPERIMENTAL: This feature is experimental...
│[?25h[?25l◇  EXPERIMENTAL: This feature is experimental...
│
◇  Git working tree is clean.
│
●  Scanning project...[999D[J●  Connecting to wizard...[999D[J●  Analyzing project structure[999D[J...

Instead of clean, readable output like:

Scanning project...
Connecting to wizard...
Analyzing project structure
Reading package.json...
Installing dependencies...
Done

Root cause

The init wizard uses @clack/prompts for its interactive UI (spinner, prompts, log messages). Clack writes raw escape sequences to stdout unconditionally — it doesn't check process.stdout.isTTY before emitting cursor movement, erase sequences, or spinner animations.

The rest of the CLI already handles this well: isPlainOutput() in src/lib/formatters/plain-detect.ts detects non-TTY contexts and gates all ANSI output, and the polling spinner in src/lib/polling.ts suppresses itself entirely when piped. But the init wizard's clack usage bypasses this system.

Expected behavior

Most modern CLIs (gh, docker, cargo) auto-detect non-TTY stdout and suppress decorations. When sentry init --yes is piped:

  • No cursor movement or erase sequences
  • No spinner animation (just plain status lines)
  • No ANSI color codes
  • Clean, grep-able output

The --yes flag already exists for non-interactive use, but the output it produces is designed for a terminal, not a log file.

Possible solution

A thin adapter module between the wizard code and @clack/prompts that checks isPlainOutput() (already exists and works) and routes to plain-text alternatives when stdout is not a TTY. The interactive prompts (select, confirm, etc.) would still pass through to real clack since they're only reached in TTY mode. This matches the pattern already used by src/lib/polling.ts.

Environment

  • sentry-cli (Node/Bun CLI)
  • @clack/prompts v0.11.0 / @clack/core v0.5.0
  • Affects any non-TTY context: pipes, redirects, CI log capture, script sessions, IDE terminals that don't report as TTY

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions