chenhg5/cc-connect · warning
gemini stt: empty response
Error message
gemini stt: empty response
What it means
Thrown by GeminiSTT.Transcribe in core/speech.go when the Gemini response parses as valid JSON but contains no candidates, or the first candidate has no content parts. The API returned 200 with a well-formed body yet no transcription text — commonly a safety-block or empty-generation result.
Source
Thrown at core/speech.go:293
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("gemini stt API %d: %s", resp.StatusCode, string(body))
}
var result struct {
Candidates []struct {
Content struct {
Parts []struct {
Text string `json:"text"`
} `json:"parts"`
} `json:"content"`
} `json:"candidates"`
}
if err := json.Unmarshal(body, &result); err != nil {
return "", fmt.Errorf("gemini stt: parse response: %w", err)
}
if len(result.Candidates) == 0 || len(result.Candidates[0].Content.Parts) == 0 {
return "", fmt.Errorf("gemini stt: empty response")
}
return strings.TrimSpace(result.Candidates[0].Content.Parts[0].Text), nil
}
// ConvertAudioToMP3 uses ffmpeg to convert audio from unsupported formats to mp3.
// Returns the mp3 bytes. If ffmpeg is not installed, returns an error.
// The ctx is honored: cancellation kills the ffmpeg subprocess, matching the
// behavior of the other Convert* helpers in this file.
func ConvertAudioToMP3(ctx context.Context, audio []byte, srcFormat string) ([]byte, error) {
ffmpegPath, err := exec.LookPath("ffmpeg")
if err != nil {
return nil, fmt.Errorf("ffmpeg not found in PATH: install ffmpeg to enable voice message support")
}
var cmd *exec.Cmd
if srcFormat == "amr" || srcFormat == "silk" {
cmd = exec.CommandContext(ctx, ffmpegPath,View on GitHub (pinned to 4000b2338a)
Solutions
- Check the audio actually contains speech and is a supported, non-corrupt format (convert with ConvertAudioToMP3).
- Log the full response to inspect candidates[0].finishReason and safetyRatings.
- Retry with a different Gemini model revision if safety filtering is the cause.
- Return a user-friendly 'could not transcribe' message rather than a system error.
- Verify the base64 audio data sent in inlineData is complete and correctly encoded.
Defensive patterns
Strategy: fallback
Validate before calling
info, err := os.Stat(audioPath)
if err != nil || info.Size() < 1024 { return errors.New("audio file too small or missing") } Try / catch
text, err := stt.Transcribe(ctx, audio)
if err != nil {
if strings.Contains(err.Error(), "empty response") {
return "", ErrNoSpeechDetected // show 'could not transcribe' to user
}
return err
} Prevention
- Pre-check audio contains actual speech before calling the API.
- Convert audio to mp3 with ConvertAudioToMP3 to avoid decode failures.
- Log finishReason/safetyRatings from the full response to distinguish safety blocks from silence.
- Offer users a retry or a different provider when transcription comes back empty.
When it happens
Trigger: result.Candidates is empty OR result.Candidates[0].Content.Parts is empty: the prompt was blocked by safety filters, the audio contained no recognizable speech, the model returned a finishReason of SAFETY/RECITATION with no parts, or a maxOutputTokens of 0 consumed everything.
Common situations: Voice message is silence/noise; audio content triggers Gemini safety filters; model revision changed default safety behavior; malformed inline audio data that decodes to nothing.
Understand the failure class
Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.
Related errors
- qwen asr: empty choices in response
- gemini stt: marshal request: %w
- gemini stt: create request: %w
- gemini stt: request: %w
- gemini stt: read response: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/8a20c9911845fd4b.
Report an issue: GitHub.