sipeed/picoclaw · error

failed to decode response: %w

Error message

failed to decode response: %w

What it means

Returned by MimoTTSProvider.Synthesize when a 200-status body does not match the expected JSON shape (choices[].message.audio.data). json.Unmarshal fails when the body is an HTML error page delivered with HTTP 200 (captive portal/gateway), empty, or when the provider changes its response schema.

Source

Thrown at pkg/audio/tts/mimo_tts.go:149

	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
	}

	var payload struct {
		Choices []struct {
			Message struct {
				Audio struct {
					Data string `json:"data"`
				} `json:"audio"`
			} `json:"message"`
		} `json:"choices"`
	}

	err = json.Unmarshal(body, &payload)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	if len(payload.Choices) == 0 || payload.Choices[0].Message.Audio.Data == "" {
		return nil, fmt.Errorf("invalid TTS response: missing audio data")
	}

	audioBytes, err := base64.StdEncoding.DecodeString(payload.Choices[0].Message.Audio.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to decode audio data: %w", err)
	}

	return io.NopCloser(bytes.NewReader(audioBytes)), nil
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Log the raw body alongside the unmarshal error to see what actually arrived
  2. Verify the resolved apiBase ends with /v1/chat/completions for api.xiaomimimo.com
  3. Whitelist the API host through proxies/captive portals
  4. Track provider changelogs for schema changes and update the payload struct
Defensive patterns

Strategy: try-catch

Type guard

func isJSONShapeError(err error) bool {
    var se *json.SyntaxError
    var te *json.UnmarshalTypeError
    return errors.As(err, &se) || errors.As(err, &te)
}

Try / catch

if err != nil && isJSONShapeError(err) {
    // 200 with non-contract JSON: check for captive portals/proxies on the path
    // and confirm apiBase still resolves to /v1/chat/completions
}

Prevention

When it happens

Trigger: A transparent proxy or captive portal returns an HTML block page with status 200; the resolved apiBase drifted away from /v1/chat/completions; the provider shipped a schema change.

Common situations: Guest/portal Wi-Fi; custom api_base normalization mistakes; provider API version bumps.

Understand the failure class

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/868666af258fb4f2. Report an issue: GitHub.