sipeed/picoclaw · error
failed to unmarshal response: %w
Error message
failed to unmarshal response: %w
What it means
Thrown when json.Unmarshal(body, &result) fails on a 200 response in ElevenLabsTranscriber.Transcribe. TranscriptionResponse has fields text/language/duration; unknown fields are ignored, so failure means the body is not JSON at all or a field has the wrong type (e.g. language as a number). A 200 with HTML text (captive portal, proxy error page) is the classic trigger.
Source
Thrown at pkg/audio/asr/elevenlabs_transcriber.go:136
}
if resp.StatusCode != http.StatusOK {
logger.ErrorCF("voice", "ElevenLabs API error", map[string]any{
"status_code": resp.StatusCode,
"response": string(body),
})
return nil, fmt.Errorf("ElevenLabs API error (status %d): %s", resp.StatusCode, string(body))
}
logger.DebugCF("voice", "Received response from ElevenLabs API", map[string]any{
"status_code": resp.StatusCode,
"response_size_bytes": len(body),
})
var result TranscriptionResponse
if err := json.Unmarshal(body, &result); err != nil {
logger.ErrorCF("voice", "Failed to unmarshal response", map[string]any{"error": err})
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
logger.InfoCF("voice", "ElevenLabs transcription completed successfully", map[string]any{
"text_length": len(result.Text),
"language": result.Language,
"transcription_preview": utils.Truncate(result.Text, 50),
})
return &result, nil
}
func (t *ElevenLabsTranscriber) Name() string {
return "elevenlabs"
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- Log the first ~200 bytes of the body (it is already available in the error path) to see what actually came back.
- If a proxy is in play, bypass it or fix its error pages to preserve status codes.
- If the endpoint is a custom gateway, point apiBase at the real https://api.elevenlabs.io or adapt the response type.
- Decode with json.Decoder + DisallowUnknownFields off, but pre-check content-type to fail fast on non-JSON.
Example fix
// before
var result TranscriptionResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
// after: fail fast with context on non-JSON bodies
if !strings.Contains(resp.Header.Get("Content-Type"), "json") {
return nil, fmt.Errorf("unexpected non-JSON response (content-type %s): %s",
resp.Header.Get("Content-Type"), utils.Truncate(string(body), 200))
}
var result TranscriptionResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response (body prefix %s): %w", utils.Truncate(string(body), 200), err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Cheap probe when using a custom apiBase: assert the endpoint speaks Scribe JSON.
// (Run once against /v1/speech-to-text with a 1-byte wav; expect JSON content-type.)
if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "json") {
return fmt.Errorf("endpoint returned non-JSON content-type %s", ct)
} Type guard
func isJSONBody(b []byte) bool {
return json.Valid(b)
} Try / catch
if _, err := el.Transcribe(ctx, path); err != nil {
var syntaxErr *json.SyntaxError
if errors.As(err, &syntaxErr) {
// 200 + non-JSON body: proxy captive portal or gateway envelope; not retryable as-is
return errors.New("ElevenLabs endpoint returned 200 with a non-JSON body — check proxy/gateway")
}
var typeErr *json.UnmarshalTypeError
if errors.As(err, &typeErr) {
return fmt.Errorf("schema drift: field %s has unexpected type", typeErr.Field)
}
return err
} Prevention
- Point apiBase at the real API or a faithful gateway, not an enveloping proxy.
- Check Content-Type before parsing in your own HTTP code.
- Pin API versions; re-run contract tests after gateway upgrades.
When it happens
Trigger: Body is an HTML interstitial from a captive portal or misconfigured proxy that answers 200; a custom apiBase (self-hosted gateway) returns a different JSON shape where 'duration' is a string; truncated body after a connection drop mid-read (ReadAll usually catches this first).
Common situations: Public-WiFi captive portals intercepting HTTPS-less traffic; internal API gateways returning their own envelope {code, data:{text}} instead of the raw Scribe shape; schema drift after an ElevenLabs API version change.
Related errors
- parse response: %w
- decode JSON response: %w
- failed to send request: %w
- parsing usage response: %w
- parsing device code response: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/10b4e5dd491bf0b0.
Report an issue: GitHub.