fix(sound): suppress ALSA stderr errors on headless systems#116
Conversation
On Linux systems without audio hardware (Docker containers, headless servers), ALSA prints error messages like 'ALSA lib confmisc.c: cannot find card 0' directly to stderr before Rust error handling can catch them. This fix temporarily redirects stderr to /dev/null during audio initialization on Linux to suppress these noisy messages while preserving the graceful fallback to terminal bell notifications. The fix: - Only applies on Linux (where ALSA is used) - Falls back to normal behavior if stderr redirection fails - Properly restores stderr after initialization - Has no effect on non-Linux platforms (macOS, Windows)
Greptile OverviewGreptile SummaryThis PR reduces noisy ALSA messages on headless Linux by wrapping Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| src/cortex-tui/src/sound.rs | Adds try_create_output_stream() that redirects stderr to /dev/null on Linux to suppress ALSA noise during rodio::OutputStream::try_default(). Main concerns: restoration calls ignore dup2/close return values, and redirection is process-wide which can drop unrelated stderr output in other threads. |
Sequence Diagram
sequenceDiagram
participant Main as cortex-tui main
participant Sound as sound::init()
participant Thr as audio thread
participant OS as Linux kernel (fd table)
participant ALSA as ALSA/rodio backend
Main->>Sound: init()
Sound->>Thr: spawn "cortex-audio"
Thr->>OS: open("/dev/null")
Thr->>OS: dup(2) (save stderr)
Thr->>OS: dup2(dev_null, 2) (redirect stderr)
Thr->>ALSA: rodio::OutputStream::try_default()
ALSA-->>OS: write(2, "ALSA lib ...")
OS-->>OS: discard to /dev/null
Thr->>OS: dup2(saved, 2) (restore stderr)
Thr->>OS: close(saved)
loop sound requests
Sound-->>Thr: SyncSender::try_send(SoundType)
Thr->>ALSA: play_wav_internal(handle, wav)
end
| // Try to create the audio output stream (ALSA errors will go to /dev/null) | ||
| let result = rodio::OutputStream::try_default(); | ||
|
|
||
| // Restore the original stderr | ||
| // SAFETY: dup2 and close are safe with valid file descriptors | ||
| unsafe { | ||
| libc::dup2(original_stderr, 2); | ||
| libc::close(original_stderr); | ||
| } |
There was a problem hiding this comment.
Stderr restoration unchecked
After redirecting stderr, the restore path ignores return values from libc::dup2(original_stderr, 2) and libc::close(original_stderr) (src/cortex-tui/src/sound.rs:112-115). If either fails (e.g., dup2 gets EBADF/EINTR), stderr can remain redirected for the rest of the process, which is a behavior change outside audio init. At minimum, check dup2’s return value and, on failure, log (and avoid closing original_stderr if you still need it).
Also appears in the redirect path where dup2(dev_null, 2) failure is handled but the restore attempt isn’t re-validated.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cortex-tui/src/sound.rs
Line: 107:115
Comment:
**Stderr restoration unchecked**
After redirecting stderr, the restore path ignores return values from `libc::dup2(original_stderr, 2)` and `libc::close(original_stderr)` (`src/cortex-tui/src/sound.rs:112-115`). If either fails (e.g., `dup2` gets `EBADF`/`EINTR`), stderr can remain redirected for the rest of the process, which is a behavior change outside audio init. At minimum, check `dup2`’s return value and, on failure, log (and avoid closing `original_stderr` if you still need it).
Also appears in the redirect path where `dup2(dev_null, 2)` failure is handled but the restore attempt isn’t re-validated.
How can I resolve this? If you propose a fix, please make it concise.| // Redirect stderr to /dev/null | ||
| // SAFETY: dup2 is safe with valid file descriptors | ||
| let redirect_result = unsafe { libc::dup2(dev_null.as_raw_fd(), 2) }; | ||
| drop(dev_null); // Close our handle to /dev/null | ||
|
|
||
| if redirect_result == -1 { | ||
| // dup2 failed, restore and try without suppression | ||
| // SAFETY: close is safe with a valid file descriptor | ||
| unsafe { libc::close(original_stderr) }; | ||
| return match rodio::OutputStream::try_default() { | ||
| Ok((stream, handle)) => Some((stream, handle)), | ||
| Err(e) => { | ||
| tracing::debug!("Failed to initialize audio output: {}", e); | ||
| None | ||
| } | ||
| }; |
There was a problem hiding this comment.
Global stderr redirection race
This temporarily redirects the process-wide fd 2 in the audio thread (src/cortex-tui/src/sound.rs:89-104). In a multi-threaded CLI, any concurrent writes to stderr (including from other libraries) during this window will be dropped, which can hide unrelated errors. To avoid losing diagnostics, consider performing the redirection as early as possible (before starting other threads) or gating it behind a mutex/global guard so only one thread can run it while other stderr writes are paused (or document the trade-off explicitly if you’re accepting it).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cortex-tui/src/sound.rs
Line: 89:104
Comment:
**Global stderr redirection race**
This temporarily redirects the *process-wide* fd 2 in the audio thread (`src/cortex-tui/src/sound.rs:89-104`). In a multi-threaded CLI, any concurrent writes to stderr (including from other libraries) during this window will be dropped, which can hide unrelated errors. To avoid losing diagnostics, consider performing the redirection as early as possible (before starting other threads) or gating it behind a mutex/global guard so only one thread can run it while other stderr writes are paused (or document the trade-off explicitly if you’re accepting it).
How can I resolve this? If you propose a fix, please make it concise.…-off Address Greptile code review feedback: 1. Check dup2 return value during stderr restoration and log errors via tracing::error if restoration fails. Only close original_stderr if dup2 succeeds, preserving it for potential debugging. 2. Add comprehensive documentation about the thread safety implications of temporarily redirecting process-wide stderr during ALSA init.
Problem
When starting the
cortexCLI on Linux systems without audio hardware (Docker containers, headless servers, CI environments), ALSA prints error messages directly to stderr:These messages are printed by the ALSA C library before Rust's error handling can catch the failure, making it impossible to suppress them through normal Rust error handling.
Solution
This PR implements temporary stderr redirection on Linux during audio initialization:
dup(2)/dev/nullusingdup2()rodio::OutputStream::try_default()(ALSA errors go to /dev/null)dup2()and close the saved fdThe fix:
Testing
cargo check -p cortex-tuicargo check -p cortex-tui --no-default-featuresChanges
src/cortex-tui/src/sound.rs: Addedtry_create_output_stream()function that wraps audio initialization with ALSA stderr suppression on Linux