cjpais/Handy · error
Failed to open recorder: {}
Error message
Failed to open recorder: {} What it means
The final failure of rec.open(device) in start_microphone_stream: the first open already failed, the code invalidated the device/config cache, re-resolved the device from a fresh enumeration, retried once, and the retry also failed. Inner errors come from cpal stream creation — device gone, unsupported config, or a host-level error (ALSA 'device or resource busy', macOS permission denied, Windows device disabled).
Source
Thrown at src-tauri/src/managers/audio.rs:638
let resolve_elapsed = resolve_started.elapsed();
// Ensure VAD is loaded if it wasn't for whatever reason
let vad_started = Instant::now();
self.preload_vad()?;
let vad_elapsed = vad_started.elapsed();
let open_started = Instant::now();
let mut recorder_opt = self.recorder.lock().unwrap();
if let Some(rec) = recorder_opt.as_mut() {
if let Err(first_err) = rec.open(selected_device.clone()) {
// A cached device or config may have gone stale (unplugged,
// rate/format changed). Re-resolve from a fresh enumeration and
// retry once before surfacing the error.
warn!("Recorder open failed ({first_err}); re-resolving device and retrying once");
self.invalidate_device_cache();
let fresh_device = self.get_effective_microphone_device(&settings);
rec.open(fresh_device)
.map_err(|e| anyhow::anyhow!("Failed to open recorder: {}", e))?;
}
}
debug!(
"mic stream breakdown: device_resolve={:?} vad_ensure={:?} open={:?}",
resolve_elapsed,
vad_elapsed,
open_started.elapsed()
);
*open_flag = true;
// This timing covers through cpal's stream.play() returning — i.e. the
// point cpal surfaces as "stream running." It does NOT guarantee the
// host audio device is producing samples yet; the first input callback
// fires asynchronously one buffer period later (hardware dependent,
// typically ~10–200ms on macOS, longer on Bluetooth/USB).
info!(
"Microphone stream initialized in {:?}",
start_time.elapsed()View on GitHub (pinned to 98a4d80cce)
Solutions
- On macOS, grant Microphone permission in System Settings > Privacy & Security
- Pick a different input device in Handy settings and retry
- Close other apps that may hold the microphone exclusively
- Re-plug/re-select the device, or switch the system default away and back to force a fresh profile
- If it persists, check the inner {e}: an unsupported-config error points at the device's rate/format, a permission/busy error at OS access
Defensive patterns
Strategy: retry
Validate before calling
// confirm the device still exists before opening
let devices = list_input_devices()?;
if !devices.iter().any(|d| d.name == selected.name) {
// fall back to the system default instead of opening a dead device
selected = default_input_device()?;
} Try / catch
// the code already retries once after re-enumeration; wrap the final error with device context
rec.open(device).map_err(|e| anyhow::anyhow!(
"cannot open input device {:?} (retry after re-enumeration also failed): {e}",
device.name()
))? Prevention
- Handle macOS microphone TCC permission proactively — check and prompt at startup, not at first open failure
- Re-select the input device after unplugging hardware instead of trusting the cached selection
- Watch for the unsupported-config inner error and clear the cached rate/format before retrying
When it happens
Trigger: Selected USB mic unplugged between enumeration and open; another app holds the device exclusively; macOS microphone TCC permission not granted; Linux device grabbed by a different audio server (PipeWire/JACK/pulse exclusivity); cached sample rate/format no longer supported after a device mode change (the retry should heal this one — failing twice means it is not a stale-cache problem).
Common situations: First recording after boot with a Bluetooth headset still negotiating its profile; macOS users denying the mic permission prompt; default device switching while Handy is idle; devices that expose only rates the stream config does not accept.
Related errors
- Failed to create AudioRecorder: {}
- threshold must be between 0.0 and 1.0
- Failed to create VAD: {e}
- Failed to create SileroVad: {}
- Failed to resolve VAD path: {}
AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16).
Data as JSON: /api/errors/7e33c332915c0221.
Report an issue: GitHub.