chenhg5/cc-connect · error

gemini stt: parse response

Error message

gemini stt: parse response: %w

What it means

Thrown by GeminiSTT.Transcribe in core/speech.go when the 200-OK body from Gemini cannot be unmarshaled into the expected {candidates:[{content:{parts:[{text}]}}]} structure. The underlying json.Unmarshal error is wrapped with 'gemini stt: parse response:'. Means the server answered successfully but with an unexpected body format.

Solutions

  1. Log the raw body to see what was actually received.
  2. Check BaseURL points at the official generativelanguage.googleapis.com endpoint (or a proxy that mirrors the exact v1beta schema).
  3. Retry — truncation is usually transient.
  4. Update the parsing struct in core/speech.go if the Gemini schema changed.
Defensive patterns

Strategy: try-catch

Try / catch

text, err := stt.Transcribe(ctx, audio)
if err != nil {
    if strings.Contains(err.Error(), "parse response") {
        slog.Warn("gemini stt: unexpected 200 body", "err", err)
        return "", ErrUnexpectedResponse
    }
    return err
}

Prevention

When it happens

Trigger: json.Unmarshal fails on the 200 body: response truncated mid-JSON, a proxy replaced the body with HTML, or the API version at BaseURL returns a different generateContent schema than the parsing struct expects.

Common situations: BaseURL pointing at a non-standard Gemini-compatible proxy with a different response shape; intermittent network truncation; api version mismatch (e.g. v1 vs v1beta differences after Google changes).

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at core/speech.go:290

	if err != nil {
		return "", fmt.Errorf("gemini stt: read response: %w", err)
	}

	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")
	}

View on GitHub (pinned to 4000b2338a)