Billionmail/BillionMail · error
decode lipsync response: %w
Error message
decode lipsync response: %w
What it means
SubmitLipSync wraps json.Decoder errors when the 2xx response body cannot be decoded into LipSyncResponse. The provider returned a success status but the body was not the expected JSON shape — an HTML error page behind a proxy, an empty body, truncated output, or a schema change to the response fields.
Source
Thrown at core/internal/service/video_gen/lipsync.go:130
if err != nil {
return "", err
}
httpReq = httpReq.WithContext(ctx)
resp, err := cfg.doHTTP(httpReq)
if err != nil {
return "", fmt.Errorf("lipsync API call: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("lipsync API error %d: %s", resp.StatusCode, string(body))
}
var result LipSyncResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("decode lipsync response: %w", err)
}
return result.ID, nil
}
// CheckLipSyncStatus checks the status of a lip sync job.
func CheckLipSyncStatus(ctx context.Context, cfg LipSyncConfig, jobID string) (*LipSyncResponse, error) {
httpReq, err := BuildLipSyncStatusRequest(cfg, jobID)
if err != nil {
return nil, err
}
httpReq = httpReq.WithContext(ctx)
resp, err := cfg.doHTTP(httpReq)
if err != nil {
return nil, fmt.Errorf("lipsync status API call: %w", err)
}
defer resp.Body.Close()
View on GitHub (pinned to fc36c76c05)
Solutions
- Capture and inspect the raw response body and Content-Type header before decoding; reject non-JSON content types early
- Verify the status-URL/submit-URL points at the API endpoint, not a gateway or dashboard route
- Check the provider changelog for response schema changes and update LipSyncResponse field tags
- Retry the request — truncated bodies from transient gateway issues often succeed on retry
- On failure, log resp.Header.Get("Content-Type") and a body prefix for diagnosis
Example fix
// before
var result LipSyncResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { ... }
// after
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
raw, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("unexpected content-type %q: %.200s", ct, raw)
}
var result LipSyncResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { ... } Defensive patterns
Strategy: type-guard
Validate before calling
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
raw, _ := io.ReadAll(resp.Body)
return fmt.Errorf("expected JSON, got %q: %.200s", ct, raw)
} Type guard
func looksLikeJSON(ct string, body []byte) bool {
if !strings.Contains(ct, "application/json") { return false }
var probe map[string]any
return json.Unmarshal(body, &probe) == nil
} Try / catch
id, err := SubmitLipSync(ctx, cfg, ...)
if err != nil {
if strings.Contains(err.Error(), "decode lipsync response") {
// log Content-Type and a raw-body prefix; retry once, then surface to ops
}
return err
} Prevention
- Always check Content-Type before JSON-decoding third-party responses
- Pin/monitor the provider API version and subscribe to changelogs
- Keep json.RawMessage of unexpected bodies for postmortems
- Add a contract test decoding a recorded real response fixture into LipSyncResponse
When it happens
Trigger: Provider returns 200/201 with an empty body, non-JSON content (HTML login/error page from a proxy or gateway), malformed/truncated JSON, or renamed the id field in a new API version so decoding succeeds structurally but fields shift (this error fires only on syntactically invalid JSON).
Common situations: Corporate proxy or CDN intercepting responses; provider outage where a gateway returns 200 with an HTML maintenance page; response body cut off by an intermediary; hitting the wrong URL (e.g. UI endpoint instead of API endpoint) that returns HTML.
Related errors
- decode lipsync status: %w
- marshal lipsync request: %w
- lipsync API error %d: %s
- decode clone response: %w
- failed to unmarshal tool call arguments: %v, %s
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/58522d49499c1f8b.
Report an issue: GitHub.