Kuberwastaken/claurst · error · anyhow::Error
Voice unavailable
Error message
Voice unavailable
What it means
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.
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.
Example fix
// before
voice.start_recording(&event_tx).await?;
// after
let avail = voice.check_availability();
if !avail.is_available() {
eprintln!("voice unavailable: {:?}", avail.error_message());
return Ok(());
}
voice.start_recording(&event_tx).await?; Defensive patterns
Strategy: validation
Validate before calling
let availability = voice.check_availability();
if !availability.is_available() {
return Err(anyhow::anyhow!(
availability.error_message().unwrap_or_default()
));
} Prevention
- Always call check_availability() before start_recording.
- Request microphone permission at app startup, not at record time.
- Disable/hide voice UI when availability fails.
When it happens
Trigger: Calling start_recording when check_availability() reports unavailable — no microphone permissions, no audio input subsystem, or platform limitations.
Common situations: App lacks microphone permission (OS privacy settings); headless/SSH environment without audio devices; voice feature disabled at compile time; no input device present.
Understand the failure class
Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.
Related errors
- No API key found for voice transcription. Set…
- Voice recording is not available in this build (compile…
- No input device available
- Whisper API returned
- voice thread runtime
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/e270bf12f0cb11e0.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/voice.rs:236
///
/// This is a non-blocking call: audio capture and transcription run on
/// Tokio tasks that stay alive until `stop_recording` is called (or the
/// recorder is dropped).
pub async fn start_recording(
&mut self,
event_tx: mpsc::Sender<VoiceEvent>,
) -> anyhow::Result<()> {
if self.is_recording.load(Ordering::SeqCst) {
return Ok(());
}
let availability = self.check_availability();
if !availability.is_available() {
let msg = availability
.error_message()
.unwrap_or_else(|| "Voice unavailable".to_string());
let _ = event_tx.send(VoiceEvent::Error(msg.clone())).await;
return Err(anyhow::anyhow!(msg));
}
self.is_recording.store(true, Ordering::SeqCst);
let is_recording = self.is_recording.clone();
let config = self.config.clone();
// cpal::Stream is !Send, so we can't use tokio::spawn (which requires Send).
// Instead, spin up a dedicated OS thread with its own single-threaded tokio
// runtime so the stream stays local to that thread throughout its lifetime.
std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("voice thread runtime");
rt.block_on(async move {
match record_and_transcribe(is_recording, event_tx.clone(), config).await {
Ok(()) => {}
View on GitHub (pinned to b0637c97ec)