{"record":{"id":"36f1f7459a965719","repo":"Billionmail/BillionMail","slug":"decode-clone-response-w","errorCode":null,"errorMessage":"decode clone response: %w","messagePattern":"decode clone response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/voice.go","lineNumber":161,"sourceCode":"\tif err != nil {\n\t\treturn nil, err\n\t}\n\thttpReq = httpReq.WithContext(ctx)\n\n\tresp, err := cfg.doHTTP(httpReq)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cartesia clone API call: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"cartesia clone API error %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar result VoiceCloneResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode clone response: %w\", err)\n\t}\n\treturn &result, nil\n}\n\n// TextToSpeech generates audio from text using a Cartesia voice.\n// Returns the path to the output WAV file.\nfunc TextToSpeech(ctx context.Context, cfg VoiceConfig, voiceID, transcript, filename string) (string, error) {\n\tif err := os.MkdirAll(cfg.OutputDir, 0755); err != nil {\n\t\treturn \"\", fmt.Errorf(\"create output dir: %w\", err)\n\t}\n\n\treq := TTSRequest{\n\t\tVoiceID:      voiceID,\n\t\tTranscript:   transcript,\n\t\tModelID:      \"sonic-2\",\n\t\tOutputFormat: DefaultTTSOutputFormat(),\n\t\tLanguage:     \"en\",\n\t}","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/voice.go#L143-L179","documentation":"After a successful (HTTP 200) call to Cartesia /voices/clone, CloneVoice decodes the response body into VoiceCloneResponse{ID,Name}. If the body is not valid JSON or does not match the expected shape, json.Decoder returns an error wrapped as 'decode clone response'. This happens when the endpoint returns 200 with an unexpected payload (proxy interference, API shape change, HTML error page).","triggerScenarios":"Cartesia returns 200 with an empty body, a changed JSON schema (field renames), a gateway/CDN HTML interstitial, or a truncated response due to network interruption mid-read.","commonSituations":"Corporate proxy or antivirus injecting HTML pages, Cartesia API version drift changing the response format, nil/empty body on 200, truncation from connection reset mid-body.","solutions":["Log the raw response body on decode failure to see what was actually returned.","Confirm Cartesia-Version header matches a version whose clone response has {id,name}.","Check for proxies/gateways rewriting responses; test from a clean network.","Use json.NewDecoder with a fresh read and check for EOF/empty body before decoding.","Pin/upgrade the API version header to the current documented one."],"exampleFix":"// before\nvar result VoiceCloneResponse\nif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n    return nil, fmt.Errorf(\"decode clone response: %w\", err)\n}\n// after\nraw, _ := io.ReadAll(resp.Body)\nvar result VoiceCloneResponse\nif err := json.Unmarshal(raw, &result); err != nil {\n    return nil, fmt.Errorf(\"decode clone response %q: %w\", string(raw), err)\n}\nif result.ID == \"\" {\n    return nil, fmt.Errorf(\"clone response missing id: %s\", string(raw))\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isValidCloneResponse(r *video_gen.VoiceCloneResponse) bool {\n    return r != nil && r.ID != \"\"\n}\n\n// usage\nresult, err := video_gen.CloneVoice(ctx, cfg, name, audioURL)\nif err != nil {\n    return fmt.Errorf(\"clone voice: %w\", err)\n}\nif !isValidCloneResponse(result) {\n    return errors.New(\"clone response missing voice id\")\n}","tryCatchPattern":"result, err := video_gen.CloneVoice(ctx, cfg, name, audioURL)\nif err != nil {\n    if strings.Contains(err.Error(), \"decode clone response\") {\n        log.Printf(\"unexpected cartesia response, check API version/proxy: %v\", err)\n        return err\n    }\n    return err\n}","preventionTips":["Pin and periodically review the Cartesia-Version header against the docs","Log raw bodies on decode failures to catch schema drift early","Test against the real API shape (or recorded fixtures) in CI","Watch for proxies/AV injecting HTML into API responses"],"tags":["json","decoding","cartesia","api"],"backgroundTag":"json-decode-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}