{"record":{"id":"22c2833d330ac7ae","repo":"zeroclaw-labs/zeroclaw","slug":"google-stt-requires-a-file-extension","errorCode":null,"errorMessage":"Google STT requires a file extension","messagePattern":"Google STT requires a file extension","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-channels/src/transcription.rs","lineNumber":705,"sourceCode":"            .map(String::from)\n            .collect()\n    }\n\n    async fn transcribe(&self, audio_data: &[u8], file_name: &str) -> Result<String> {\n        let (normalized_name, _) = validate_audio(audio_data, file_name)?;\n\n        let encoding = match normalized_name\n            .rsplit_once('.')\n            .map(|(_, e)| e.to_ascii_lowercase())\n            .as_deref()\n        {\n            Some(\"flac\") => \"FLAC\",\n            Some(\"wav\") => \"LINEAR16\",\n            Some(\"ogg\" | \"opus\") => \"OGG_OPUS\",\n            Some(\"mp3\") => \"MP3\",\n            Some(\"webm\") => \"WEBM_OPUS\",\n            Some(ext) => bail!(\"Google STT does not support '.{ext}' input\"),\n            None => bail!(\"Google STT requires a file extension\"),\n        };\n\n        let audio_content =\n            base64::Engine::encode(&base64::engine::general_purpose::STANDARD, audio_data);\n\n        let request_body = serde_json::json!({\n            \"config\": {\n                \"encoding\": encoding,\n                \"languageCode\": &self.language_code,\n                \"enableAutomaticPunctuation\": true,\n            },\n            \"audio\": {\n                \"content\": audio_content,\n            }\n        });\n\n        let resp = self\n            .build_request(&request_body)?","sourceCodeStart":687,"sourceCodeEnd":723,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/transcription.rs#L687-L723","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Pass the real filename including extension into transcribe()","If the original name is lost, derive an extension from the MIME type (e.g. audio/ogg -> .ogg) before calling","Default unknown bare names to the extension that matches how you captured the audio (Telegram voice notes are .ogg)"],"exampleFix":"// before\nlet text = manager.transcribe(&audio, \"voice_message\").await?;\n\n// after: derive a usable name from the Telegram MIME type\nlet name = extension_for_audio_mime(mime).map(|e| format!(\"voice.{e}\"))\n    .unwrap_or_else(|| \"voice.ogg\".to_string());\nlet text = manager.transcribe(&audio, &name).await?;","handlingStrategy":"validation","validationCode":"fn has_audio_extension(file_name: &str) -> bool {\n    file_name.rsplit_once('.').is_some_and(|(_, e)| !e.is_empty())\n}","typeGuard":"fn has_audio_extension(file_name: &str) -> bool {\n    matches!(file_name.rsplit_once('.'), Some((_, e)) if !e.is_empty())\n}","tryCatchPattern":null,"preventionTips":["Always pass the original upload filename through to transcribe()","If names are synthesized, derive an extension from the MIME type or default to .ogg for Telegram voice notes","Cover the no-extension case in channel tests with a bare-name fixture"],"tags":["google","stt","missing-extension","transcription","pre-flight-check"],"backgroundTag":"unsupported-file-format","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}