{"record":{"id":"e270bf12f0cb11e0","repo":"Kuberwastaken/claurst","slug":"voice-unavailable","errorCode":null,"errorMessage":"Voice unavailable","messagePattern":"Voice unavailable","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-rust/crates/core/src/voice.rs","lineNumber":236,"sourceCode":"    ///\r\n    /// This is a non-blocking call: audio capture and transcription run on\r\n    /// Tokio tasks that stay alive until `stop_recording` is called (or the\r\n    /// recorder is dropped).\r\n    pub async fn start_recording(\r\n        &mut self,\r\n        event_tx: mpsc::Sender<VoiceEvent>,\r\n    ) -> anyhow::Result<()> {\r\n        if self.is_recording.load(Ordering::SeqCst) {\r\n            return Ok(());\r\n        }\r\n\r\n        let availability = self.check_availability();\r\n        if !availability.is_available() {\r\n            let msg = availability\r\n                .error_message()\r\n                .unwrap_or_else(|| \"Voice unavailable\".to_string());\r\n            let _ = event_tx.send(VoiceEvent::Error(msg.clone())).await;\r\n            return Err(anyhow::anyhow!(msg));\r\n        }\r\n\r\n        self.is_recording.store(true, Ordering::SeqCst);\r\n\r\n        let is_recording = self.is_recording.clone();\r\n        let config = self.config.clone();\r\n\r\n        // cpal::Stream is !Send, so we can't use tokio::spawn (which requires Send).\r\n        // Instead, spin up a dedicated OS thread with its own single-threaded tokio\r\n        // runtime so the stream stays local to that thread throughout its lifetime.\r\n        std::thread::spawn(move || {\r\n            let rt = tokio::runtime::Builder::new_current_thread()\r\n                .enable_all()\r\n                .build()\r\n                .expect(\"voice thread runtime\");\r\n            rt.block_on(async move {\r\n                match record_and_transcribe(is_recording, event_tx.clone(), config).await {\r\n                    Ok(()) => {}\r","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/core/src/voice.rs#L218-L254","documentation":"start_recording checks availability() first; if voice is unavailable it sends a VoiceEvent::Error and returns Err using the availability's error message, falling back to the generic 'Voice unavailable'. It signals the failure both through the event channel and the Result.","triggerScenarios":"Calling start_recording when check_availability() reports unavailable — no microphone permissions, no audio input subsystem, or platform limitations.","commonSituations":"App lacks microphone permission (OS privacy settings); headless/SSH environment without audio devices; voice feature disabled at compile time; no input device present.","solutions":["Grant microphone access in OS privacy/mic settings.","Check availability() before starting and surface the specific error_message() to the user.","Connect a microphone / run in an environment with an audio input device."],"exampleFix":"// before\nvoice.start_recording(&event_tx).await?;\n// after\nlet avail = voice.check_availability();\nif !avail.is_available() {\n    eprintln!(\"voice unavailable: {:?}\", avail.error_message());\n    return Ok(());\n}\nvoice.start_recording(&event_tx).await?;","handlingStrategy":"validation","validationCode":"let availability = voice.check_availability();\nif !availability.is_available() {\n    return Err(anyhow::anyhow!(\n        availability.error_message().unwrap_or_default()\n    ));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always call check_availability() before start_recording.","Request microphone permission at app startup, not at record time.","Disable/hide voice UI when availability fails."],"tags":["voice","audio","availability"],"backgroundTag":"unsupported-platform","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}