Skip to content

Audio.start should support silent failure without writing ALSA diagnostics to stderr #1358

Open

Description

@muyiyr

Description

Audio.start() writes a large ALSA diagnostics block directly to the host process stderr when Linux has libasound2 but no playback device. The method then correctly returns false and emits a structured error event, but TUI hosts cannot prevent the native stderr from overwriting their terminal UI.

Please add an explicit silent startup option, such as Audio.start({ silent: true }) or an equivalent setup option. Silent mode should suppress native backend diagnostics while preserving the existing false result and structured error event.

Reproduction

Environment:

  • @opentui/core 0.5.1
  • Bun 1.3.14
  • Linux ARM64 container
  • libasound2 installed
  • no /dev/snd
import { Audio } from "@opentui/core"

const audio = Audio.create({ autoStart: false })
audio.on("error", (error, context) => {
  console.error("AUDIO_ERROR", context.action, context.status, error.message)
})

for (let attempt = 1; attempt <= 3; attempt++) {
  console.error("ATTEMPT", attempt)
  audio.start()
}

 audio.dispose()

Run with stderr captured:

bun repro.ts 2> repro.stderr

Observed result:

  • 105 lines beginning with ALSA lib
  • 3 structured Audio start failed: -5 error events
  • each start() call emits another 35-line ALSA diagnostics block for default, dmix, dmix:0, and dmix:0,0

Representative output:

ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
...
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM dmix:0,0
AUDIO_ERROR start -5 Audio start failed: -5

Expected behavior

A host application should be able to opt into silent audio initialization so an unavailable optional playback device does not corrupt a terminal UI. Failure should remain observable through the return value and error event.

Additional context

OpenCode currently mitigates repeated output by disabling attention audio for the remainder of the TUI lifecycle after the first failed startup: anomalyco/opencode#41770. That bounds the output to one diagnostics block but cannot suppress the first native block without support at this boundary.

Activity

  1. spiatrenka commented on Aug 17, 2026

    @spiatrenka

    Same root cause from another direction, in case it helps scope this: hosts can't contain JavaScript-level writes either, not just native ones.

    Verified against @opentui/core 0.5.3 (and 0.4.5, which opencode pins):

    • consoleMode: "console-overlay" monkey-patches global.console, so console.* is contained.
    • process.stdout.write / process.stderr.write are not. externalOutputMode only ever swaps stdout.write (renderer.ts:1262, :1790), and its one capturing value is rejected outside screenMode: "split-footer" (renderer.ts:367).
    • stderr never appears as a process.stderr hook in core — only the synthetic CapturedWritableStream used to build the replacement Console, plus an internal capture-buffer type label.

    So any dependency logging via process.stderr.write paints over the alternate screen. In opencode that surfaces as a plugin logger destroying the TUI frame (anomalyco/opencode#43064); we're working around it host-side by patching both wrappers for the renderer's lifetime (anomalyco/opencode#43065).

    A host-level containment option covering both JS and native writes would subsume the Audio.start({ silent: true }) ask here.

  2. dajiaohuang commented on Aug 18, 2026

    @dajiaohuang
    Contributor

    Current main has two distinct containment gaps: the renderer can replace console.* and, in split-footer mode, stdout.write, but it does not contain process.stderr.write; native ALSA diagnostics also write directly to fd 2, beyond a JavaScript stream wrapper.

    Could a maintainer choose the intended scope here?

    1. A narrow Audio.start({ silent: true })-style option implemented at the native audio backend/log-callback boundary, suppressing expected initialization diagnostics, or
    2. Renderer-level stdout/stderr containment for alternate-screen applications, which would need to cover both JavaScript streams and native fd writes and is a substantially broader behavior change.

    If the narrow route is preferred, it would also help to specify whether silent suppresses only backend initialization diagnostics or every native audio log. The two designs have different public APIs and cannot be safely inferred from the current issue discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions