{"record":{"id":"f2a256787422539c","repo":"Billionmail/BillionMail","slug":"cartesia-clone-api-call-w","errorCode":null,"errorMessage":"cartesia clone API call: %w","messagePattern":"cartesia clone API call: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/voice.go","lineNumber":150,"sourceCode":"// CloneVoice creates a cloned voice from an audio sample URL via Cartesia API.\nfunc CloneVoice(ctx context.Context, cfg VoiceConfig, name, audioURL string) (*VoiceCloneResponse, error) {\n\treq := VoiceCloneRequest{\n\t\tName:        name,\n\t\tDescription: fmt.Sprintf(\"Cloned voice for %s\", name),\n\t\tMode:        \"url\",\n\t\tAudioURL:    audioURL,\n\t\tLanguage:    \"en\",\n\t}\n\n\thttpReq, err := BuildCloneRequest(cfg, req)\n\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) {","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/voice.go#L132-L168","documentation":"CloneVoice executes the prepared Cartesia clone request via cfg.doHTTP and wraps client-side transport failures with 'cartesia clone API call: %w'. This error fires before any HTTP status check — it means the request never completed, not that the API returned an error payload (non-200 responses produce the separate 'cartesia clone API error %d' error).","triggerScenarios":"cfg.doHTTP(httpReq) returns a *url.Error: DNS resolution failure, connection refused/timeout, TLS handshake failure, or the request context (attached via httpReq.WithContext(ctx)) was cancelled before a response arrived.","commonSituations":"No outbound internet or blocked egress to api.cartesia.ai; DNS outage; expired/invalid API key is NOT this error (that returns non-200 and hits the status branch); pipeline shutdown or per-call timeout cancelling ctx mid-request.","solutions":["Unwrap with errors.As(err, &urlErr) and check errors.Is(err, context.DeadlineExceeded)/context.Canceled to classify timeout vs shutdown vs network","Verify outbound connectivity to the Cartesia host (curl https://api.cartesia.ai from the same container)","Increase the context timeout for large voice sample uploads, and check the base URL scheme/host","Retry once with backoff for transient network errors before failing the pipeline","Confirm the API key is valid only if the request completes with a non-200 status (different error path)"],"exampleFix":"// before\nresp, err := cfg.doHTTP(httpReq)\nif err != nil {\n    return nil, fmt.Errorf(\"cartesia clone API call: %w\", err)\n}\n// after\nresp, err := cfg.doHTTP(httpReq)\nif err != nil {\n    if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n        return nil, fmt.Errorf(\"cartesia clone call cancelled or timed out: %w\", err)\n    }\n    var urlErr *url.Error\n    if errors.As(err, &urlErr) && isTransient(urlErr) {\n        resp, err = retryWithBackoff(httpReq)\n        if err != nil {\n            return nil, fmt.Errorf(\"cartesia clone API call after retry: %w\", err)\n        }\n        return resp, nil\n    }\n    return nil, fmt.Errorf(\"cartesia clone API call: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"if ctx.Err() != nil {\n    return fmt.Errorf(\"clone aborted before call: %w\", ctx.Err())\n}\nif netErr := checkConnectivity(\"https://api.cartesia.ai\"); netErr != nil {\n    return fmt.Errorf(\"cartesia unreachable: %w\", netErr)\n}","typeGuard":null,"tryCatchPattern":"clone, err := video_gen.CloneVoice(ctx, cfg, req)\nif err != nil {\n    var urlErr *url.Error\n    switch {\n    case errors.Is(err, context.Canceled):\n        // caller cancelled; no retry\n    case errors.Is(err, context.DeadlineExceeded):\n        // increase timeout or shrink sample payload, then retry\n    case errors.As(err, &urlErr) && isTransientNetError(urlErr):\n        // retry with exponential backoff\n    default:\n        return fmt.Errorf(\"voice clone failed: %w\", err)\n    }\n}","preventionTips":["Give voice-clone calls a context timeout sized for sample upload time","Distinguish context.Canceled/DeadlineExceeded from network errors via errors.Is before retrying","Monitor egress/DNS health in the deployment environment","Retry only transient *url.Error causes (connection refused/reset, timeouts), never after cancellation"],"tags":["network","http","cartesia","timeout","dns"],"backgroundTag":"request-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}