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

  1. Check the audio actually contains speech and is a supported, non-corrupt format (convert with ConvertAudioToMP3).
  2. Log the full response to inspect candidates[0].finishReason and safetyRatings.
  3. Retry with a different Gemini model revision if safety filtering is the cause.
  4. Return a user-friendly 'could not transcribe' message rather than a system error.
  5. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/8a20c9911845fd4b. Report an issue: GitHub.