cjpais/Handy · error · std::io::Error
No input device found
Error message
No input device found
What it means
Returned as a std::io::Error with kind NotFound when cpal's host cannot produce a default input device. The recorder asks the platform audio host (CoreAudio, WASAPI, ALSA/PulseAudio/PipeWire) for default_input_device() and converts a None into this error before any stream is built. It is a propagated Err, not a panic, so callers can match on it.
Source
Thrown at src-tauri/src/audio_toolkit/audio/recorder.rs:178
if !self.needs_reopen() {
return Ok(()); // already open
}
log::warn!("Capture stream failed; rebuilding microphone stream");
let _ = self.close();
}
self.stream_error.store(false, Ordering::Relaxed);
let (sample_tx, sample_rx) = mpsc::channel::<AudioChunk>();
let (cmd_tx, cmd_rx) = mpsc::channel::<Cmd>();
let (init_tx, init_rx) = mpsc::sync_channel::<Result<(), String>>(1);
let host = crate::audio_toolkit::get_cpal_host();
let device = match device {
Some(dev) => dev,
None => host
.default_input_device()
.ok_or_else(|| Error::new(std::io::ErrorKind::NotFound, "No input device found"))?,
};
let thread_device = device.clone();
let vad = self.vad.clone();
// Move the optional level callback into the worker thread
let level_cb = self.level_cb.clone();
// Move the optional real-time audio frame callback into the worker thread
let audio_cb = self.audio_cb.clone();
let selected_channel = self.selected_channel;
let config_cache = Arc::clone(&self.config_cache);
let stream_error = Arc::clone(&self.stream_error);
let worker = std::thread::spawn(move || {
let stop_flag = Arc::new(AtomicBool::new(false));
let stop_flag_for_stream = stop_flag.clone();
let init_result = (|| -> Result<(cpal::Stream, u32), String> {
let config_started = Instant::now();
let device_name = thread_device.name().unwrap_or_default();View on GitHub (pinned to c6fa60da2f)
Solutions
- Connect a microphone and set it as the default input device in OS sound settings
- Select an explicit input device in Handy settings instead of relying on the OS default (the Some(dev) branch bypasses default_input_device entirely)
- On bare Linux, install and start PipeWire or PulseAudio so cpal's host can enumerate inputs
- In containers/WSL/remote sessions, expose or forward an audio device, or run in a session that has one
Example fix
// before
let device = host
.default_input_device()
.ok_or_else(|| Error::new(std::io::ErrorKind::NotFound, "No input device found"))?;
// after — surface device-count context so support can tell no-mic from broken-audio-server
let device = match host.default_input_device() {
Some(d) => d,
None => {
let visible = host.input_devices().map(|mut it| it.count()).unwrap_or(0);
return Err(Box::new(Error::new(
std::io::ErrorKind::NotFound,
format!("No default input device found ({visible} input devices visible)"),
)));
}
}; Defensive patterns
Strategy: validation
Validate before calling
// Before entering a recording state, confirm an input exists
let has_input = crate::audio_toolkit::get_cpal_host()
.input_devices()
.map(|mut it| it.next().is_some())
.unwrap_or(false);
if !has_input {
// show a "connect a microphone" state instead of starting the recorder
} Type guard
fn input_device_available(host: &cpal::Host) -> bool {
host.default_input_device().is_some()
} Prevention
- Enumerate input devices before arming the record control and disable it when the list is empty
- Persist the user's chosen device id and validate it still exists on each start instead of always using the default
- On Linux, verify an audio server (PipeWire/PulseAudio) is running before launching the app
When it happens
Trigger: Calling the record/start path without an explicit device while host.default_input_device() returns None: no microphone attached, no default capture device selected in OS sound settings, or a session where the audio server exposes no inputs (bare ALSA, headless/SSH, some containers, non-audio remote desktop sessions).
Common situations: Desktop or server with no mic; USB mic unplugged between device-list refresh and record start; fresh Linux install or WSL/container without PulseAudio/PipeWire; remote-desktop session that does not forward capture devices; default device pointing at a disconnected Bluetooth headset.
Related errors
- {error_message}
- Failed to create AudioRecorder: {}
- Failed to open recorder: {}
- Failed to create resampler
- Failed to initialize recording manager
AI-assisted analysis of cjpais/Handy@c6fa60da2f (2026-08-17).
Data as JSON: /api/errors/08d8bf766fde9de7.
Report an issue: GitHub.