You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
tui: ALSA errors flood and corrupt the terminal during interaction #41763
On the V2 beta/next TUI, ordinary interaction can repeatedly initialize ALSA on a Linux host with no sound card. Scrolling through the slash-command picker and continuing to interact with the session prints ALSA diagnostics directly over the TUI, corrupting the display.
Expected behavior: ordinary TUI input should not initialize audio, and subprocess/library stderr should not overwrite the terminal UI.
Representative output:
ALSA lib confmisc.c:855:(parse_card) cannot find card '0'
ALSA lib conf.c:5180:(_snd_config_evaluate) function snd_func_card_inum returned error: No such file or directory
ALSA lib conf.c:422:(snd_func_concat) error evaluating strings
ALSA lib conf.c:5180:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1334:(snd_func_refer) error evaluating name
ALSA lib conf.c:5180:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5703:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2664:(snd_pcm_open_noupdate) Unknown PCM default
The sequence repeats for default, dmix, and dmix:0,0.
Install/channel: npm global install, V2 next channel.
Shell: /usr/bin/zsh.
Terminal environment: tmux (TERM_PROGRAM=tmux, TERM=xterm-256color, COLORTERM=truecolor).
Background service: running at http://127.0.0.1:49374.
Plugins
@plannotator/opencode@latest
@ishaksebsib/opencode-tree
Local clankerhouse-lifecycle.ts
OpenCode version
opencode2 v0.0.0-next-17155
Steps to reproduce
Run opencode2 on Linux without an available ALSA sound card.
Type / to open the slash-command picker.
Scroll through the commands or otherwise interact with the session.
Observe repeated ALSA errors written over the TUI.
Screenshot and/or share link
A screenshot is available, showing the repeated ALSA output overwriting the command picker and session UI.
Operating System
Linux 6.1.175-219.359.amzn2023.aarch64 (aarch64)
Terminal
tmux (TERM=xterm-256color)
Reacted by Antonio de Perio, Ryan Yip, Adam Hosker and Yuchen Shi
If your issue is distinct (ALSA-specific triggering path, or the plugin list is relevant), it may still warrant a separate fix, but linking these could help the maintainers triage.
I reproduced a deterministic code-level failure on current dev. @opentui/core's Audio.start() returns false and keeps the instance unstarted when no playback device is available, while packages/tui/src/audio.ts calls start() again on every play attempt. A focused test shows two play calls currently trigger two startup attempts.
I have a small local patch that disposes the failed engine, clears cached sounds, and treats audio as unavailable until the TUI lifecycle resets it, plus regression coverage. This bounds the native/ALSA startup diagnostics to the first attempted sound. @kommander, let me know if you want me to send the PR or if you are already handling it.
Linux ARM64 validation is complete in a container with libasound2 installed and no /dev/snd. Three playback attempts produced 105 ALSA lib stderr lines and three Audio start failed: -5 events before the patch, versus 35 lines and one startup failure after it. This confirms the patch stops repeated ALSA initialization blocks within one TUI lifecycle; the first native startup attempt can still emit one diagnostics block. Draft PR: #41770.
The repeated ALSA startup path is now fixed in #41770 and the first native diagnostics block is tracked upstream in anomalyco/opentui#1358.
To identify why scrolling the / menu coincided with an attention sound request, could you capture only the four built-in notification event families? This intentionally avoids prompts, tool input/output, file contents, and provider payloads.
Then restart OpenCode, reproduce the / menu scrolling until the ALSA block appears, note its wall-clock time, quit, and share ~/.cache/opencode/attention-timeline.log plus that timestamp. Please remove the plugin afterward.
This will tell us whether the sound request came from a question, permission, session completion, session error, or from outside the built-in notification path. Please redact session IDs if needed.
I reproduced this in tmux on Linux ARM64 with no /dev/snd.
On opencode2 v0.0.0-next-17155, I started a fresh standalone TUI, submitted a short prompt, immediately opened /, and scrolled. When the session completed, one 35-line ALSA diagnostics block was written directly over the command picker and visibly corrupted it. Scrolling by itself did not emit ALSA output; the coincident trigger was the session-completion attention sound.
Two completed prompts in one fresh TUI lifecycle produced 70 ALSA lib lines total (35 per completion), with default, dmix, dmix:0, and dmix:0,0 each attempted twice. That confirms repeated audio startup attempts on this build.
I also tried the requested timeline plugin. Placing it at ~/.config/opencode/plugins/attention-timeline.ts caused the server plugin loader to claim it and reject it (Missing key at ["default"]["effect"] / ["default"]["setup"]), so no timeline file was created. In this next-channel build, TUI plugins are discovered under plugins/tui/, and the runtime ABI had changed from { id, tui(api) } to { id, setup(context) }; events are exposed through context.data.listen(...) as { name, details }.
OpenCode auto-updated while I investigated this from next-17155 to next-17288. I adapted the plugin at ~/.config/opencode/plugins/tui/attention-timeline.ts and mapped the current event names (form.created, permission.asked, session.execution.succeeded, session.execution.failed, and session.step.failed) to the four requested families. It loaded successfully, and a completion recorded:
So the available evidence points to the completion notification path, not menu scrolling, as the source of the sound request. Note that the repeated 35-line behavior above was measured before the automatic update, on next-17155.
Second host confirming the same failure, plus a repro of the noise source without OpenCode
Confirming this on WSL2 with a TUI and no audio device. Filing the extra details here rather than keeping a separate ticket — my own report (#55302, now closed as a duplicate) is fully covered by this issue.
Environment: opencode v2.0.28 (latest channel, ~/.opencode/bin/opencode), Ubuntu 22.04 on WSL2 (6.18.33.2-microsoft-standard-WSL2), Windows Terminal; TERM=xterm-256color, TERM_PROGRAM/COLORTERM unset.
Audio device state:
ls /dev/snd # only "timer", no pcm*
cat /proc/asound/cards # No such file or directory
ls /run/user/$(id -u)/pulse # empty — no PulseAudio servercommand -v paplay pactl aplay ffplay # none installed
The noise is alsa-lib's, not OpenCode's output — it reproduces without the TUI at all, which rules the renderer out:
ALSA lib confmisc.c:855:(parse_card) cannot find card '0'
ALSA lib conf.c:5182:(_snd_config_evaluate) function snd_func_card_inum returned error: No such file or directory
ALSA lib confmisc.c:422:(snd_func_concat) error evaluating strings
ALSA lib conf.c:5182:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1334:(snd_func_refer) error evaluating name
ALSA lib conf.c:5182:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5705:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2664:(snd_pcm_open_noupdate) Unknown PCM default
rc= -2
Trigger: any attention event while attention.sound: true — a permission request is enough, the turn does not need to finish:
100% reproducible here. Only the TUI client is affected: ~/.local/share/opencode/log/opencode.log contains nothing matching alsa|audio|sound, and opencode service status plus GET /api/info report a healthy service — so the write happens inside the client process, on the terminal it owns.
Version note: in v2.0.28 the client ships a miniaudio-based audio engine (loadSoundFile/play) that dlopens libasound.so.2 / libpulse.so.0, with bundled mp3 packs mapped per event (default/done = bip-bop-01, question = bip-bop-03, permission = staplebops-06, error = nope-03, subagent_done = yup-01). PulseAudio is tried before ALSA, which is probably why a desktop with a working Pulse server never hits this.
Workarounds on this host:attention.sound: false, or opencode 2>/tmp/opencode.err (keeps the noise off the screen; sound still cannot play). attention.notifications: true is unaffected — only the audio path corrupts the screen.
Fix:#51854 (skip audio init when no playback device exists) addresses this directly. Until it lands, redirecting or suppressing the native backend's stderr on the UI path would also prevent the corruption.
Related: #52793 (WSL2, same trigger), #55302 (duplicate of this issue).
but the TUI user is not in the audio group, so the device open fails with EACCES — net effect identical to the no-card case: the ~35-line ALSA lib ... diagnostics block is written over the TUI on every attention sound (session completion in our case, matching the trigger established earlier in this thread).
Still 100% reproducible on v2.0.32 (latest as of 2026-10-17).
Workaround confirmed on this host: "attention": { "sound": false } in ~/.config/opencode/cli.json hot-reloads without a TUI restart and the ALSA block disappears entirely. Alternatively usermod -aG audio <user> + re-login makes the device openable and the diagnostics stop as well.
Confirming on v2.0.32 / Ubuntu 26.04 on WSL2 (no PCM in /dev/snd, WSLg audio server reachable)
Adding one more host to this thread instead of opening a new ticket. #52793 describes the same trigger; posting here because this is where the fix tracking and the most recent reproductions live.
OS: Linux 6.18.33.2-microsoft-standard-WSL2, Ubuntu 26.04.1 LTS, WSLg active
Terminal: Windows Terminal via WSLg, TERM=xterm-256color, TERM_PROGRAM and COLORTERM unset, shell /bin/bash
Config: ~/.config/opencode/cli.json with "attention": { "sound": true, "notifications": true } explicitly set (the binary default resolves to false)
Audio state
$ ls /dev/snd # timer only, no pcm*
$ cat /proc/asound/cards # No such file or directory
$ dpkg -l | grep -E 'libasound2-plugins|libpulse'# neither installed
$ grep -E 'defaults\.(pcm|ctl)\.card' /usr/share/alsa/alsa.conf
defaults.ctl.card 0
defaults.pcm.card 0
So pcm.default expands to card 0, which does not exist, and there is no pulse plugin to fall back to.
Trigger — deterministic, once per completed turn, at the moment the "done" attention sound would play. The same 8-line block repeats for default, dmix, dmix:0 and dmix:0,0, is written over the alternate screen, and the frame stays damaged: the burst lands immediately after the end of a synchronized-output block (ESC[?2026l), so the renderer never repaints over it and the terminal keeps the raw text.
One detail that may matter for the fix: this host does have a reachable audio server. WSLg exports PULSE_SERVER=unix:/mnt/wslg/PulseServer and the socket is live, and the binary references libpulse.so.0, but the playback path only goes through ALSA hw/default and never falls back to Pulse. ~/.local/share/opencode/log/opencode.log stays clean (no alsa|audio|sound lines), consistent with the earlier analysis that the bytes are client-side stderr rather than server output.
Workaround being applied (from #52793): install libasound2-plugins + libpulse0 and add ~/.asoundrc with pcm.!default { type pulse } / ctl.!default { type pulse }, which keeps sounds working through WSLg's RDP sink; otherwise "attention": { "sound": false } removes the trigger entirely.
Would it be reasonable for the audio path to (a) try PulseAudio before ALSA before hw, and (b) treat "no usable device" as a permanent no-op after the first failure? Per this thread the repeated-startup half was fixed in #41770, but the first diagnostics block still reaches the pty on v2.0.32.
Verified workaround on v2.0.32 / WSL2 — but routing default alone is not enough
Follow-up to my confirmation above: the PulseAudio routing workaround is now verified end-to-end on this host. One detail may matter for the real fix: on a card-less system it is not enough to make default work, because the audio path also probes dmix variants that fail independently.
pcm.!default { type pulse }
ctl.!default { type pulse }
# alsa.conf:364 defines pcm.dmix as a reference (pcm.dmix cards.pcm.dmix),
# so a plain block fails with "dmix is not a compound" and breaks the whole
# config load; it must be overridden with the "!" prefix.
pcm.!dmix {
@args [ CARD DEVICE ]
@args.CARD { type string default "0" }
@args.DEVICE { type string default "0" }
type pulse
}
Results (per device, counting ALSA lib lines on stderr)
Device
Before
After
default
card 0 errors
rc=0, 0 lines
dmix
5 lines
rc=0, 0 lines
dmix:0
5 lines
rc=0, 0 lines
dmix:0,0
5 lines
rc=0, 0 lines
Also verified: speaker-test -D default -c 2 -r 44100 -t sine -l 1 plays without diagnostics, amixer -D default scontrols lists Master/Capture (so the ctl side needs the override too), and aplay -L emits 0 error lines.
Only adding pcm.!default { type pulse } — the workaround documented in #52793 — was not sufficient here: dmix, dmix:0 and dmix:0,0 still produced the same 5-line card-0 block each, i.e. the same corruption, because those names expand through defaults.pcm.card 0 and never go through pcm.!default. With sound enabled, the TUI was clean only after all four names were covered.
Note also that a TUI restart was needed for the change to take effect, consistent with the comment above that a failed Audio.start() leaves the instance unstarted and cached; the already-damaged frame is not repainted either.
Suggestion for the fix: besides capturing native backend stderr, don't probe card-expanding fallbacks (dmix:*) on a host with no card, or resolve the device list from snd_device_name_hint and skip names that expand to a missing card. On this host default and dmix* were exactly the entries that made Audio.start() fail.
Config left in place: "attention": { "sound": true } in ~/.config/opencode/cli.json.
Still reproducible on stable v2.1.2, with one difference from the original report: this box has sound cards. It's the ALSA default device that's broken.
Debian 13 headless, no PipeWire/PulseAudio.
TUI inside herdr, over SSH from Windows (Zed remote), TERM_PROGRAM=zed.
attention.sound: true in cli.json.
During an active session, the attention sound fires often enough to drop this over the TUI every few seconds:
ALSA lib pcm_dmix.c:1000:(snd_pcm_dmix_open) unable to open slave
Resizing repaints the frame, then it comes back on the next sound.
Confirmed it's the audio path: libasound.so.2 is mapped in the TUI process, and opening default from Python prints the same line and returns -2, while plughw:1,0 and plughw:0,3 open fine (0). So dmix has no valid slave, rather than there being no card at all.
Workaround: "attention": { "sound": false } (notifications stay on). It would be enough if audio init stopped retrying after the first failure and backend stderr never reached the TUI's tty.
2.1.4 on Linux aarch64 (headless server, no /dev/snd, no card), still reproduces the first-diagnostic-block behavior described above.
Reproduction details:
~/.config/opencode/cli.json has "attention": {"sound": true, "notifications": true}.
Events that request a sound in real sessions (interrupted turns, permission prompts) construct the opentui Audio engine. Verified two ways:
/proc/<tui-pid>/maps in a live session shows libasound.so.2 mapped (8 mappings).
The same binary boots that never request a sound show zero libasound mappings and zero ALSA lines in stderr.
Each failed engine/start probe sends the block to stderr, which shares the tty with the TUI frame. Observed live over SSH (Termius): the ALSA fragment lines painted over session content, e.g. leading fragments of cannot find card '0', Unknown PCM dmix:0, and ard index for 0 interleaved between frames, matching the representative output above.
Interaction with resize repaint: a resize repaints the frame, then the block returns on the next sound event, matching the earlier comment in this thread.
Observed stderr block (single probe, Linux aarch64):
ALSA lib confmisc.c:855:(parse_card) cannot find card '0'
ALSA lib conf.c:5208:(_snd_config_evaluate) function snd_func_card_id returned error: No such file or directory
ALSA lib confmisc.c:422:(snd_func_concat) error evaluating strings
ALSA lib conf.c:5731:(_snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM dmix:0
ALSA lib confmisc.c:1342:(snd_func_refer) error evaluating name
ALSA lib confmisc.c:165:(snd_config_get_card) Cannot get card index for 0
The line batch repeats per probe (default, dmix, dmix:0,0), so an interrupted turn paints it multiple times while the frame streams.
The 2.1.4 release does not contain the retry-stop change from #41770 (that PR was closed by the cleanup bot without merge). Workarounds that fully stop the spill here: "attention": {"sound": false} in cli.json, or an ALSA null default device via ~/.asoundrc so probes succeed silently.
Thanks for the detailed report, I was able to reproduce this.
I ran it on Linux with libasound2 installed and no sound card (/dev/snd absent), in tmux with TERM=xterm-256color, on @opencode/[email protected] (your next-17155 build is no longer published). With attention.sound enabled, every finished turn plays the "done" sound. Each time, the ALSA lines (cannot find card '0' … Unknown PCM default, dmix, dmix:0, dmix:0,0) are drawn straight over the session, prompt and footer. The failed audio start is retried on every sound, so the block of about 35 lines comes back each time.
In my run, scrolling the / picker on its own didn't trigger anything. The output only appeared when an attention sound played (turn done, error, permission or question).
It still happens on the latest dev (a97dc23). The video and screenshot below are made from tmux pane captures of the run. The script below reproduces it when run in tmux with VISIBLE=1.
Workaround until this is fixed: turn sounds off with "attention": { "sound": false } in your TUI config.
Related: #52939 (same ALSA output, still open) and the open PR #59128, which redirects the TUI's stderr away from the screen.
recording.mp4
Reproduction script (drive.ts)
import{Effect}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"// Linux host with libasound2 installed but no sound card (/dev/snd absent),// TUI config attention.sound = true.// Each finished turn plays the "done" attention sound -> ALSA init fails and// libasound writes its diagnostics to the TUI's stderr, i.e. onto the screen.//// Headless (default): records the session; TUI stderr goes to drive's log.// VISIBLE=1 (run inside tmux): the TUI draws into the real terminal and the// ALSA lines appear over it; inspect with `tmux capture-pane -p`.constvisible=!!process.env.VISIBLEexportdefaultOpenCodeDriver.use({opencode: {
...(process.env.OC_DEV ? {dev: process.env.OC_DEV} : {}),
...(visible ? {visible: true} : {}),},tui: {recording: !visible},// Attention sounds on (the author's setup plays them; current default is off).tuiConfig: {attention: {sound: true}},},({ ui, llm })=>Effect.gen(function*(){yield*llm.title(()=>Effect.succeed("ALSA repro"))yield*ui.type("/")for(leti=0;i<5;i++)yield*ui.arrow("down")yield*ui.screenshot("slash-picker")yield*ui.press("backspace")for(constnof[1,2,3]){yield*llm.queue(Llm.text(`Reply number ${n}.`))yield*ui.submit(`say ${n}`)yield*ui.waitFor(`Reply number ${n}.`)yield*Effect.sleep("3 seconds")yield*ui.screenshot(`after-turn-${n}`)}if(visible)yield*Effect.sleep("20 seconds")// time to capture the pane}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Activity
github-actions commentedon Aug 11, 2026
This issue might be a duplicate of, or closely related to, existing issues about subprocess/library stderr corrupting the TUI. Please check:
If your issue is distinct (ALSA-specific triggering path, or the plugin list is relevant), it may still warrant a separate fix, but linking these could help the maintainers triage.
muyiyr commentedon Aug 11, 2026
I reproduced a deterministic code-level failure on current dev. @opentui/core's Audio.start() returns false and keeps the instance unstarted when no playback device is available, while packages/tui/src/audio.ts calls start() again on every play attempt. A focused test shows two play calls currently trigger two startup attempts.
I have a small local patch that disposes the failed engine, clears cached sounds, and treats audio as unavailable until the TUI lifecycle resets it, plus regression coverage. This bounds the native/ALSA startup diagnostics to the first attempted sound. @kommander, let me know if you want me to send the PR or if you are already handling it.
adeperio commentedon Aug 12, 2026
Looks like my issue too #41890
muyiyr commentedon Aug 12, 2026
Linux ARM64 validation is complete in a container with
libasound2installed and no/dev/snd. Three playback attempts produced 105ALSA libstderr lines and threeAudio start failed: -5events before the patch, versus 35 lines and one startup failure after it. This confirms the patch stops repeated ALSA initialization blocks within one TUI lifecycle; the first native startup attempt can still emit one diagnostics block. Draft PR: #41770.muyiyr commentedon Aug 12, 2026
The repeated ALSA startup path is now fixed in #41770 and the first native diagnostics block is tracked upstream in anomalyco/opentui#1358.
To identify why scrolling the
/menu coincided with an attention sound request, could you capture only the four built-in notification event families? This intentionally avoids prompts, tool input/output, file contents, and provider payloads.Create
~/.config/opencode/plugins/attention-timeline.ts:Then restart OpenCode, reproduce the
/menu scrolling until the ALSA block appears, note its wall-clock time, quit, and share~/.cache/opencode/attention-timeline.logplus that timestamp. Please remove the plugin afterward.This will tell us whether the sound request came from a question, permission, session completion, session error, or from outside the built-in notification path. Please redact session IDs if needed.
tsah commentedon Aug 12, 2026
I reproduced this in tmux on Linux ARM64 with no
/dev/snd.On
opencode2 v0.0.0-next-17155, I started a fresh standalone TUI, submitted a short prompt, immediately opened/, and scrolled. When the session completed, one 35-line ALSA diagnostics block was written directly over the command picker and visibly corrupted it. Scrolling by itself did not emit ALSA output; the coincident trigger was the session-completion attention sound.Two completed prompts in one fresh TUI lifecycle produced 70
ALSA liblines total (35 per completion), withdefault,dmix,dmix:0, anddmix:0,0each attempted twice. That confirms repeated audio startup attempts on this build.I also tried the requested timeline plugin. Placing it at
~/.config/opencode/plugins/attention-timeline.tscaused the server plugin loader to claim it and reject it (Missing key at ["default"]["effect"]/["default"]["setup"]), so no timeline file was created. In this next-channel build, TUI plugins are discovered underplugins/tui/, and the runtime ABI had changed from{ id, tui(api) }to{ id, setup(context) }; events are exposed throughcontext.data.listen(...)as{ name, details }.OpenCode auto-updated while I investigated this from
next-17155tonext-17288. I adapted the plugin at~/.config/opencode/plugins/tui/attention-timeline.tsand mapped the current event names (form.created,permission.asked,session.execution.succeeded,session.execution.failed, andsession.step.failed) to the four requested families. It loaded successfully, and a completion recorded:So the available evidence points to the completion notification path, not menu scrolling, as the source of the sound request. Note that the repeated 35-line behavior above was measured before the automatic update, on
next-17155.adeperio commentedon Aug 12, 2026
To work around I just disabled sound. Obviously not useful if you want that feature
aydemir commentedon Sep 28, 2026
Reproduced on Android 16 + Termux (aarch64, no audio device), opencode v2.0.18, with a controlled toggle:
Suspected path:
packages/tui/src/audio.ts(Audio.create/start) init failure escapes to the terminal instead of the debug log.AlexeyShirshov commentedon Oct 14, 2026
Second host confirming the same failure, plus a repro of the noise source without OpenCode
Confirming this on WSL2 with a TUI and no audio device. Filing the extra details here rather than keeping a separate ticket — my own report (#55302, now closed as a duplicate) is fully covered by this issue.
Environment: opencode
v2.0.28(latest channel,~/.opencode/bin/opencode), Ubuntu 22.04 on WSL2 (6.18.33.2-microsoft-standard-WSL2), Windows Terminal;TERM=xterm-256color,TERM_PROGRAM/COLORTERMunset.Audio device state:
The noise is alsa-lib's, not OpenCode's output — it reproduces without the TUI at all, which rules the renderer out:
python3 -c "import ctypes; a=ctypes.CDLL('libasound.so.2'); h=ctypes.c_void_p(); print('rc=', a.snd_pcm_open(ctypes.byref(h), b'default', 0, 0))"Trigger: any attention event while
attention.sound: true— a permission request is enough, the turn does not need to finish:100% reproducible here. Only the TUI client is affected:
~/.local/share/opencode/log/opencode.logcontains nothing matchingalsa|audio|sound, andopencode service statusplusGET /api/inforeport a healthy service — so the write happens inside the client process, on the terminal it owns.Version note: in
v2.0.28the client ships a miniaudio-based audio engine (loadSoundFile/play) that dlopenslibasound.so.2/libpulse.so.0, with bundled mp3 packs mapped per event (default/done=bip-bop-01,question=bip-bop-03,permission=staplebops-06,error=nope-03,subagent_done=yup-01). PulseAudio is tried before ALSA, which is probably why a desktop with a working Pulse server never hits this.Workarounds on this host:
attention.sound: false, oropencode 2>/tmp/opencode.err(keeps the noise off the screen; sound still cannot play).attention.notifications: trueis unaffected — only the audio path corrupts the screen.Fix: #51854 (skip audio init when no playback device exists) addresses this directly. Until it lands, redirecting or suppressing the native backend's stderr on the UI path would also prevent the corruption.
Related: #52793 (WSL2, same trigger), #55302 (duplicate of this issue).
earneet commentedon Oct 17, 2026
Another environment variant: sound card present but inaccessible (EACCES), still reproducible on v2.0.32
attention.sound: trueexplicitly enabled in~/.config/opencode/cli.json(noted the binary default resolves tofalse, this host had it enabled)Difference from prior reports: this host has a sound card and a populated
/dev/snd:but the TUI user is not in the
audiogroup, so the device open fails with EACCES — net effect identical to the no-card case: the ~35-lineALSA lib ...diagnostics block is written over the TUI on every attention sound (session completion in our case, matching the trigger established earlier in this thread).Two notes for the fix in #51854:
/dev/sndpresence or card enumeration, this permission-denied variant still slips through — the check needs an actual device open attempt, or the opentui-level stderr suppression (Audio.start should support silent failure without writing ALSA diagnostics to stderr opentui#1358) which covers every failure reason regardless.Workaround confirmed on this host:
"attention": { "sound": false }in~/.config/opencode/cli.jsonhot-reloads without a TUI restart and the ALSA block disappears entirely. Alternativelyusermod -aG audio <user>+ re-login makes the device openable and the diagnostics stop as well.MardorTC commentedon Oct 18, 2026
Confirming on v2.0.32 / Ubuntu 26.04 on WSL2 (no PCM in
/dev/snd, WSLg audio server reachable)Adding one more host to this thread instead of opening a new ticket. #52793 describes the same trigger; posting here because this is where the fix tracking and the most recent reproductions live.
Environment
v2.0.32, latest channel,~/.opencode/bin/opencode(Bun-compiled ELF)Linux 6.18.33.2-microsoft-standard-WSL2, Ubuntu 26.04.1 LTS, WSLg activeTERM=xterm-256color,TERM_PROGRAMandCOLORTERMunset, shell/bin/bash~/.config/opencode/cli.jsonwith"attention": { "sound": true, "notifications": true }explicitly set (the binary default resolves tofalse)Audio state
So
pcm.defaultexpands to card 0, which does not exist, and there is no pulse plugin to fall back to.Trigger — deterministic, once per completed turn, at the moment the "done" attention sound would play. The same 8-line block repeats for
default,dmix,dmix:0anddmix:0,0, is written over the alternate screen, and the frame stays damaged: the burst lands immediately after the end of a synchronized-output block (ESC[?2026l), so the renderer never repaints over it and the terminal keeps the raw text.One detail that may matter for the fix: this host does have a reachable audio server. WSLg exports
PULSE_SERVER=unix:/mnt/wslg/PulseServerand the socket is live, and the binary referenceslibpulse.so.0, but the playback path only goes through ALSAhw/defaultand never falls back to Pulse.~/.local/share/opencode/log/opencode.logstays clean (noalsa|audio|soundlines), consistent with the earlier analysis that the bytes are client-side stderr rather than server output.Workaround being applied (from #52793): install
libasound2-plugins+libpulse0and add~/.asoundrcwithpcm.!default { type pulse }/ctl.!default { type pulse }, which keeps sounds working through WSLg's RDP sink; otherwise"attention": { "sound": false }removes the trigger entirely.Would it be reasonable for the audio path to (a) try PulseAudio before ALSA before
hw, and (b) treat "no usable device" as a permanent no-op after the first failure? Per this thread the repeated-startup half was fixed in #41770, but the first diagnostics block still reaches the pty on v2.0.32.MardorTC commentedon Oct 18, 2026
Verified workaround on v2.0.32 / WSL2 — but routing
defaultalone is not enoughFollow-up to my confirmation above: the PulseAudio routing workaround is now verified end-to-end on this host. One detail may matter for the real fix: on a card-less system it is not enough to make
defaultwork, because the audio path also probesdmixvariants that fail independently.Setup
sudo apt install libasound2-plugins # provides libasound_module_pcm_pulse.so; pulls libpulse0~/.asoundrc(did not exist before):Results (per device, counting
ALSA liblines on stderr)defaultdmixdmix:0dmix:0,0Also verified:
speaker-test -D default -c 2 -r 44100 -t sine -l 1plays without diagnostics,amixer -D default scontrolslistsMaster/Capture(so thectlside needs the override too), andaplay -Lemits 0 error lines.Only adding
pcm.!default { type pulse }— the workaround documented in #52793 — was not sufficient here:dmix,dmix:0anddmix:0,0still produced the same 5-line card-0 block each, i.e. the same corruption, because those names expand throughdefaults.pcm.card 0and never go throughpcm.!default. With sound enabled, the TUI was clean only after all four names were covered.Note also that a TUI restart was needed for the change to take effect, consistent with the comment above that a failed
Audio.start()leaves the instance unstarted and cached; the already-damaged frame is not repainted either.Suggestion for the fix: besides capturing native backend stderr, don't probe card-expanding fallbacks (
dmix:*) on a host with no card, or resolve the device list fromsnd_device_name_hintand skip names that expand to a missing card. On this hostdefaultanddmix*were exactly the entries that madeAudio.start()fail.Config left in place:
"attention": { "sound": true }in~/.config/opencode/cli.json.tsu-ld commentedon Oct 21, 2026
Still reproducible on stable
v2.1.2, with one difference from the original report: this box has sound cards. It's the ALSAdefaultdevice that's broken.TERM_PROGRAM=zed.attention.sound: trueincli.json.Resizing repaints the frame, then it comes back on the next sound.
Confirmed it's the audio path:
libasound.so.2is mapped in the TUI process, and openingdefaultfrom Python prints the same line and returns-2, whileplughw:1,0andplughw:0,3open fine (0). So dmix has no valid slave, rather than there being no card at all.Workaround:
"attention": { "sound": false }(notifications stay on). It would be enough if audio init stopped retrying after the first failure and backend stderr never reached the TUI's tty.wanazhar commentedon Oct 25, 2026
2.1.4 on Linux aarch64 (headless server, no
/dev/snd, no card), still reproduces the first-diagnostic-block behavior described above.Reproduction details:
~/.config/opencode/cli.jsonhas"attention": {"sound": true, "notifications": true}.Audioengine. Verified two ways:/proc/<tui-pid>/mapsin a live session showslibasound.so.2mapped (8 mappings).libasoundmappings and zero ALSA lines in stderr.cannot find card '0',Unknown PCM dmix:0, andard index for 0interleaved between frames, matching the representative output above.Observed stderr block (single probe, Linux aarch64):
The line batch repeats per probe (
default,dmix,dmix:0,0), so an interrupted turn paints it multiple times while the frame streams.The 2.1.4 release does not contain the retry-stop change from #41770 (that PR was closed by the cleanup bot without merge). Workarounds that fully stop the spill here:
"attention": {"sound": false}incli.json, or an ALSA null default device via~/.asoundrcso probes succeed silently.opencodejr commentedon Oct 28, 2026
Thanks for the detailed report, I was able to reproduce this.
I ran it on Linux with
libasound2installed and no sound card (/dev/sndabsent), in tmux withTERM=xterm-256color, on@opencode/[email protected](yournext-17155build is no longer published). Withattention.soundenabled, every finished turn plays the "done" sound. Each time, the ALSA lines (cannot find card '0'…Unknown PCM default,dmix,dmix:0,dmix:0,0) are drawn straight over the session, prompt and footer. The failed audio start is retried on every sound, so the block of about 35 lines comes back each time.In my run, scrolling the
/picker on its own didn't trigger anything. The output only appeared when an attention sound played (turn done, error, permission or question).It still happens on the latest
dev(a97dc23). The video and screenshot below are made from tmux pane captures of the run. The script below reproduces it when run in tmux withVISIBLE=1.Workaround until this is fixed: turn sounds off with
"attention": { "sound": false }in your TUI config.Related: #52939 (same ALSA output, still open) and the open PR #59128, which redirects the TUI's stderr away from the screen.
recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.