Immutable. This exact content is served forever at /api/v1/blob/c0645745ba25a629.
---
description: All stderr/bail user-facing messages must go through MessageBox
alwaysApply: false
---
# stderr output must always use `MessageBox`
Never use `eprintln!` for user-facing messages. Every message shown to the user — error, warning, or info — must go through `message_box::MessageBox`.
`eprintln!` silently bypasses the box, producing bare unformatted output regardless of context (Quiet mode, interactive mode, etc.).
## The pattern that broke this
The `is_interactive()` / `UiMode::Quiet` flag controls **interactive behaviour** (countdowns, prompts, sleeps). It must **never** suppress the `MessageBox` itself — not for errors, not for warnings.
```rust
// ❌ BAD — Quiet mode skips the box entirely
if ctx.is_interactive() {
message_box::MessageBox::new().error()...print_stderr();
sleep_or_cancel(5).await;
} else {
eprintln!("lade: {e}"); // bare, no box
}
// ❌ BAD — same mistake for warnings
if ctx.is_interactive() && !warnings.is_empty() {
message_box::MessageBox::new().warning()...print_stderr();
sleep_or_cancel(5).await;
}
// ✅ GOOD — box always; only the interactive parts are gated
let mut mb = message_box::MessageBox::new()
.error() // or .warning()
.paragraph(e.to_string());
if ctx.is_interactive() {
mb = mb.line("Waiting 5 seconds before continuing... (2x Ctrl-C to cancel)");
}
mb.print_stderr();
if ctx.is_interactive() {
sleep_or_cancel(5).await;
}
```