{"record":{"id":"08d8bf766fde9de7","repo":"cjpais/Handy","slug":"no-input-device-found","errorCode":null,"errorMessage":"No input device found","messagePattern":"No input device found","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/audio_toolkit/audio/recorder.rs","lineNumber":178,"sourceCode":"            if !self.needs_reopen() {\n                return Ok(()); // already open\n            }\n            log::warn!(\"Capture stream failed; rebuilding microphone stream\");\n            let _ = self.close();\n        }\n\n        self.stream_error.store(false, Ordering::Relaxed);\n\n        let (sample_tx, sample_rx) = mpsc::channel::<AudioChunk>();\n        let (cmd_tx, cmd_rx) = mpsc::channel::<Cmd>();\n        let (init_tx, init_rx) = mpsc::sync_channel::<Result<(), String>>(1);\n\n        let host = crate::audio_toolkit::get_cpal_host();\n        let device = match device {\n            Some(dev) => dev,\n            None => host\n                .default_input_device()\n                .ok_or_else(|| Error::new(std::io::ErrorKind::NotFound, \"No input device found\"))?,\n        };\n\n        let thread_device = device.clone();\n        let vad = self.vad.clone();\n        // Move the optional level callback into the worker thread\n        let level_cb = self.level_cb.clone();\n        // Move the optional real-time audio frame callback into the worker thread\n        let audio_cb = self.audio_cb.clone();\n        let selected_channel = self.selected_channel;\n        let config_cache = Arc::clone(&self.config_cache);\n        let stream_error = Arc::clone(&self.stream_error);\n\n        let worker = std::thread::spawn(move || {\n            let stop_flag = Arc::new(AtomicBool::new(false));\n            let stop_flag_for_stream = stop_flag.clone();\n            let init_result = (|| -> Result<(cpal::Stream, u32), String> {\n                let config_started = Instant::now();\n                let device_name = thread_device.name().unwrap_or_default();","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/cjpais/Handy/blob/c6fa60da2f13a5af660fba17f37af548855119c5/src-tauri/src/audio_toolkit/audio/recorder.rs#L160-L196","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nlet device = host\n    .default_input_device()\n    .ok_or_else(|| Error::new(std::io::ErrorKind::NotFound, \"No input device found\"))?;\n\n// after — surface device-count context so support can tell no-mic from broken-audio-server\nlet device = match host.default_input_device() {\n    Some(d) => d,\n    None => {\n        let visible = host.input_devices().map(|mut it| it.count()).unwrap_or(0);\n        return Err(Box::new(Error::new(\n            std::io::ErrorKind::NotFound,\n            format!(\"No default input device found ({visible} input devices visible)\"),\n        )));\n    }\n};","handlingStrategy":"validation","validationCode":"// Before entering a recording state, confirm an input exists\nlet has_input = crate::audio_toolkit::get_cpal_host()\n    .input_devices()\n    .map(|mut it| it.next().is_some())\n    .unwrap_or(false);\nif !has_input {\n    // show a \"connect a microphone\" state instead of starting the recorder\n}","typeGuard":"fn input_device_available(host: &cpal::Host) -> bool {\n    host.default_input_device().is_some()\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","cpal","audio","microphone","no-input-device"],"backgroundTag":"no-audio-input-device","analyzedSha":"c6fa60da2f13a5af660fba17f37af548855119c5","analyzedAt":"2026-08-17T10:29:55.597Z","contentChangedAt":"2026-08-17T10:29:55.597Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}