zeroclaw-labs/zeroclaw · warning

Google STT requires a file extension

Error message

Google STT requires a file extension

What it means

GoogleSttProvider::transcribe() derives the encoding from the file extension; when file_name contains no '.' at all, the extension Option is None and the provider bails before any API call. This is the sibling of error 289: 289 is an unrecognized extension, 290 is no extension to recognize.

Source

Thrown at crates/zeroclaw-channels/src/transcription.rs:705

            .map(String::from)
            .collect()
    }

    async fn transcribe(&self, audio_data: &[u8], file_name: &str) -> Result<String> {
        let (normalized_name, _) = validate_audio(audio_data, file_name)?;

        let encoding = match normalized_name
            .rsplit_once('.')
            .map(|(_, e)| e.to_ascii_lowercase())
            .as_deref()
        {
            Some("flac") => "FLAC",
            Some("wav") => "LINEAR16",
            Some("ogg" | "opus") => "OGG_OPUS",
            Some("mp3") => "MP3",
            Some("webm") => "WEBM_OPUS",
            Some(ext) => bail!("Google STT does not support '.{ext}' input"),
            None => bail!("Google STT requires a file extension"),
        };

        let audio_content =
            base64::Engine::encode(&base64::engine::general_purpose::STANDARD, audio_data);

        let request_body = serde_json::json!({
            "config": {
                "encoding": encoding,
                "languageCode": &self.language_code,
                "enableAutomaticPunctuation": true,
            },
            "audio": {
                "content": audio_content,
            }
        });

        let resp = self
            .build_request(&request_body)?

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Pass the real filename including extension into transcribe()
  2. If the original name is lost, derive an extension from the MIME type (e.g. audio/ogg -> .ogg) before calling
  3. Default unknown bare names to the extension that matches how you captured the audio (Telegram voice notes are .ogg)

Example fix

// before
let text = manager.transcribe(&audio, "voice_message").await?;

// after: derive a usable name from the Telegram MIME type
let name = extension_for_audio_mime(mime).map(|e| format!("voice.{e}"))
    .unwrap_or_else(|| "voice.ogg".to_string());
let text = manager.transcribe(&audio, &name).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_audio_extension(file_name: &str) -> bool {
    file_name.rsplit_once('.').is_some_and(|(_, e)| !e.is_empty())
}

Type guard

fn has_audio_extension(file_name: &str) -> bool {
    matches!(file_name.rsplit_once('.'), Some((_, e)) if !e.is_empty())
}

Prevention

When it happens

Trigger: transcribe() on the google provider with a file_name like "voice", "audio-2026-08-22", or "recording" — any string without a dot. The Google provider is the only one that hard-fails on a missing extension (Whisper-compatible providers fall back through their own normalization).

Common situations: Channel code synthesizing names from database IDs or timestamps instead of preserving the upload filename; stripping extensions in an upload pipeline; test fixtures named without extensions.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/22c2833d330ac7ae. Report an issue: GitHub.