{"record":{"id":"9bd909f50e0238b6","repo":"cjpais/Handy","slug":"error-message","errorCode":null,"errorMessage":"{error_message}","messagePattern":"\\{error_message\\}","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/audio_toolkit/audio/recorder.rs","lineNumber":359,"sourceCode":"                }\n            }\n        });\n\n        match init_rx.recv() {\n            Ok(Ok(())) => {\n                self.device = Some(device);\n                self.cmd_tx = Some(cmd_tx);\n                self.worker_handle = Some(worker);\n                Ok(())\n            }\n            Ok(Err(error_message)) => {\n                let _ = worker.join();\n                let kind = if is_microphone_access_denied(&error_message) {\n                    std::io::ErrorKind::PermissionDenied\n                } else {\n                    std::io::ErrorKind::Other\n                };\n                Err(Box::new(Error::new(kind, error_message)))\n            }\n            Err(recv_error) => {\n                let _ = worker.join();\n                Err(Box::new(Error::other(format!(\n                    \"Failed to initialize microphone worker: {recv_error}\"\n                ))))\n            }\n        }\n    }\n\n    /// Queue a recording start and return a one-shot receiver that resolves only\n    /// after the first real microphone sample chunk has entered the capture path.\n    /// `Stream::play()` returning is not sufficient: some Bluetooth and USB\n    /// devices take much longer to begin delivering callbacks.\n    pub fn start(\n        &self,\n        vad_policy: VadPolicy,\n    ) -> Result<mpsc::Receiver<()>, Box<dyn std::error::Error>> {","sourceCodeStart":341,"sourceCodeEnd":377,"githubUrl":"https://github.com/cjpais/Handy/blob/c6fa60da2f13a5af660fba17f37af548855119c5/src-tauri/src/audio_toolkit/audio/recorder.rs#L341-L377","documentation":"The microphone worker thread failed to initialize and its string message is re-raised on the caller's thread. The message originates inside the worker: 'Failed to build input stream: {e}', 'Failed to start microphone stream: {e}', 'Unsupported sample format: {format:?}', or a device-config fetch failure. is_microphone_access_denied() promotes OS permission messages ('Access is denied', 'permission denied', WASAPI 0x80070005) to ErrorKind::PermissionDenied; everything else becomes ErrorKind::Other.","triggerScenarios":"start() spawns the worker, which fetches the device config, builds a cpal input stream and calls stream.play(); any of these failing sends Err(msg) over init_tx and this branch re-raises it. Triggers: mic permission denied (macOS TCC, Windows privacy), device unplugged mid-open, device occupied, exotic sample format, or a stale cached config after the device changed rate/format in the OS.","commonSituations":"First run on macOS without granting Microphone permission; Windows microphone access toggled off in Privacy settings; recording right after unplugging a USB mic; Bluetooth headset switching profile (A2DP to HFP) between enumeration and open; devices exposing sample formats cpal cannot map.","solutions":["Grant microphone access: macOS System Settings > Privacy & Security > Microphone; Windows Settings > Privacy & Security > Microphone","Re-select the input device in settings (a stale cached config is dropped on failure, so a retry re-queries the device) and retry","Unplug/replug the mic or restart it; on Windows, disable exclusive-mode capture by other apps","If the message mentions an unsupported sample format, change the device's default rate/format in OS sound settings or update the audio driver"],"exampleFix":"// before\nif let Err(e) = recorder.start() {\n    eprintln!(\"{e}\");\n}\n\n// after — branch on the promoted error kind the start() handshake already produced\nif let Err(e) = recorder.start() {\n    if e.kind() == std::io::ErrorKind::PermissionDenied {\n        // guide user to OS microphone privacy settings\n    } else if is_no_input_device_error(&e.to_string()) {\n        // refresh the device list and prompt re-selection\n    } else {\n        log::error!(\"microphone init failed: {e}\");\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: confirm the selected device still exists before opening it\nlet device_still_present = host\n    .input_devices()?\n    .filter_map(|d| d.name().ok())\n    .any(|name| name == selected_device_name);\nif !device_still_present {\n    // re-prompt device selection before attempting start()\n}","typeGuard":"fn is_permission_error(e: &std::io::Error) -> bool {\n    e.kind() == std::io::ErrorKind::PermissionDenied\n}","tryCatchPattern":"match recorder.start() {\n    Ok(()) => {}\n    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {\n        // surface a \"grant microphone access\" action pointing at OS privacy settings\n    }\n    Err(e) => {\n        // log; the worker already dropped its stale config cache, so re-select the device and retry once\n    }\n}","preventionTips":["Check microphone authorization up front (macOS TCC status; Windows privacy policy) before the first recording","Validate the stored device id against current devices on every start and fall back to the default with a UI notice","After an open failure, re-query the device config (the code drops its cache) instead of blindly retrying the same open"],"tags":["rust","cpal","audio","microphone","permissions","stream-open"],"backgroundTag":"microphone-access-denied","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"}