cjpais/Handy · critical · anyhow::Error

Failed to create SileroVad

Error message

Failed to create SileroVad: {}

What it means

SileroVad::new failed to load or instantiate the ONNX VAD model at resources/models/silero_vad_v4.onnx. Construction spans reading the file and creating an ONNX runtime session, so failures mean: the file is missing (dev checkout that skipped the documented curl step), the file is corrupt/truncated, or the ONNX runtime cannot initialize (missing dynamic libraries or an incompatible ort build). Recording cannot start without VAD, hence the impact.

Solutions

  1. Run the documented step: curl -o src-tauri/resources/models/silero_vad_v4.onnx https://blob.handy.computer/silero_vad_v4.onnx
  2. Verify the file size/hash against the upstream blob and re-download if truncated
  3. Check that tauri.conf.json bundle.resources includes resources/models so the file ships
  4. Confirm ONNX runtime dynamic libraries are present/loaded (check the inner error text after 'Failed to create SileroVad:')

Example fix

// before
let silero = SileroVad::new(vad_path, VAD_THRESHOLD)
    .map_err(|e| anyhow::anyhow!("Failed to create SileroVad: {}", e))?;

// after — distinguish 'missing resource' from 'broken runtime' with an actionable message
if !vad_path.exists() {
    anyhow::bail!(
        "VAD model missing at {vad_path:?} — fetch silero_vad_v4.onnx (see AGENTS.md prerequisites)"
    );
}
let silero = SileroVad::new(vad_path, VAD_THRESHOLD)
    .map_err(|e| anyhow::anyhow!("Failed to create SileroVad: {}", e))?;
Defensive patterns

Strategy: validation

Validate before calling

// fail with an actionable message before ONNX runtime involvement
if !vad_path.exists() {
    anyhow::bail!(
        "VAD model missing at {vad_path:?} — run the AGENTS.md curl step or fix bundling"
    );
}
let silero = SileroVad::new(vad_path, VAD_THRESHOLD)?;

Try / catch

if let Err(e) = create_audio_recorder(&vad_path, ...) {
    if e.to_string().contains("Failed to create SileroVad") {
        // resource/packaging problem: check file existence + size before retrying,
        // and surface a 'reinstall / refetch resource' message to the user
    }
    return Err(e);
}

Prevention

When it happens

Trigger: Fresh clone built without fetching silero_vad_v4.onnx into src-tauri/resources/models (AGENTS.md prerequisite); resource not bundled because tauri.conf.json's bundle.resources omits it; onnx file truncated by an interrupted curl; ort shared libraries missing on the runtime system.

Common situations: New contributor/CI environment missing the manually-downloaded resource; packaging config drift dropping the models dir from bundles; binary copied to a machine without the required ONNX runtime libs.

Related errors


AI-assisted analysis of cjpais/Handy@c89b7bf389 (2026-08-17). Data as JSON: /api/errors/9cc9d7f9d9c9a446. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/managers/audio.rs:290

/// user's regular selected microphone is missing.
struct MicrophoneResolution {
    device: Option<cpal::Device>,
    unavailable_selected_microphone: Option<String>,
}

/* ──────────────────────────────────────────────────────────────── */

fn create_audio_recorder(
    vad_path: &Path,
    app_handle: &tauri::AppHandle,
    selected_channel: Option<u16>,
    stream_router: Arc<StreamRouter>,
) -> Result<AudioRecorder, anyhow::Error> {
    // A single Silero engine covers both the offline and streaming policies (never
    // active at once within a recording), so the recorder reconfigures its
    // hangover tail per session rather than keeping two ONNX sessions resident.
    let silero = SileroVad::new(vad_path, VAD_THRESHOLD)
        .map_err(|e| anyhow::anyhow!("Failed to create SileroVad: {}", e))?;
    let smoothed_vad = SmoothedVad::new(
        Box::new(silero),
        VAD_PREFILL_FRAMES,
        VAD_OFFLINE_HANGOVER_FRAMES,
        VAD_ONSET_FRAMES,
    );

    // Recorder with VAD, a spectrum-level callback that forwards level updates to
    // the frontend, and an audio-frame callback that feeds live streaming via a
    // shared `StreamRouter` (captured directly, not via Tauri state — see its docs).
    let recorder = AudioRecorder::new()
        .map_err(|e| anyhow::anyhow!("Failed to create AudioRecorder: {}", e))?
        .with_vad(
            Box::new(smoothed_vad),
            VAD_OFFLINE_HANGOVER_FRAMES,
            VAD_STREAMING_HANGOVER_FRAMES,
        )
        .with_selected_channel(selected_channel)

View on GitHub (pinned to c89b7bf389)