{"record":{"id":"225e5e5fe2fffebb","repo":"chenhg5/cc-connect","slug":"gemini-stt-parse-response-w","errorCode":null,"errorMessage":"gemini stt: parse response: %w","messagePattern":"gemini stt: parse response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/speech.go","lineNumber":290,"sourceCode":"\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"gemini stt: read response: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"gemini stt API %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar result struct {\n\t\tCandidates []struct {\n\t\t\tContent struct {\n\t\t\t\tParts []struct {\n\t\t\t\t\tText string `json:\"text\"`\n\t\t\t\t} `json:\"parts\"`\n\t\t\t} `json:\"content\"`\n\t\t} `json:\"candidates\"`\n\t}\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"gemini stt: parse response: %w\", err)\n\t}\n\tif len(result.Candidates) == 0 || len(result.Candidates[0].Content.Parts) == 0 {\n\t\treturn \"\", fmt.Errorf(\"gemini stt: empty response\")\n\t}\n\n\treturn strings.TrimSpace(result.Candidates[0].Content.Parts[0].Text), nil\n}\n\n// ConvertAudioToMP3 uses ffmpeg to convert audio from unsupported formats to mp3.\n// Returns the mp3 bytes. If ffmpeg is not installed, returns an error.\n// The ctx is honored: cancellation kills the ffmpeg subprocess, matching the\n// behavior of the other Convert* helpers in this file.\nfunc ConvertAudioToMP3(ctx context.Context, audio []byte, srcFormat string) ([]byte, error) {\n\tffmpegPath, err := exec.LookPath(\"ffmpeg\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"ffmpeg not found in PATH: install ffmpeg to enable voice message support\")\n\t}\n","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/speech.go#L272-L308","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Log the raw body to see what was actually received.","Check BaseURL points at the official generativelanguage.googleapis.com endpoint (or a proxy that mirrors the exact v1beta schema).","Retry — truncation is usually transient.","Update the parsing struct in core/speech.go if the Gemini schema changed."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"text, err := stt.Transcribe(ctx, audio)\nif err != nil {\n    if strings.Contains(err.Error(), \"parse response\") {\n        slog.Warn(\"gemini stt: unexpected 200 body\", \"err\", err)\n        return \"\", ErrUnexpectedResponse\n    }\n    return err\n}","preventionTips":["Point BaseURL at the official v1beta endpoint or a schema-faithful proxy.","Log raw bodies on parse failure for diagnosis.","Retry once on truncation-style failures.","Track Gemini API release notes for schema changes and update the struct."],"tags":["json","speech-to-text","gemini","parsing"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}