chenhg5/cc-connect · error

openai tts: marshal request: %w

Error message

openai tts: marshal request: %w

What it means

OpenAITTS.Synthesize builds a request body for the OpenAI-compatible /audio/speech endpoint (core/tts.go:244) and marshals it with encoding/json. This error wraps a json.Marshal failure on the map[string]any request body. In practice this is nearly unreachable since all values in the map (strings and a float64) are JSON-marshalable; it exists for defensive completeness.

Source

Thrown at core/tts.go:244

}

// Synthesize sends text to OpenAI TTS API and returns MP3 audio bytes.
func (o *OpenAITTS) Synthesize(ctx context.Context, text string, opts TTSSynthesisOpts) ([]byte, string, error) {
	voice := opts.Voice
	if voice == "" {
		voice = "alloy"
	}
	reqBody := map[string]any{
		"model": o.Model,
		"input": text,
		"voice": voice,
	}
	if opts.Speed > 0 {
		reqBody["speed"] = opts.Speed
	}
	jsonData, err := json.Marshal(reqBody)
	if err != nil {
		return nil, "", fmt.Errorf("openai tts: marshal request: %w", err)
	}

	url := strings.TrimRight(o.BaseURL, "/") + "/audio/speech"
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(jsonData))
	if err != nil {
		return nil, "", fmt.Errorf("openai tts: create request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+o.APIKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := o.Client.Do(req)
	if err != nil {
		return nil, "", fmt.Errorf("openai tts: request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect any recently added fields in reqBody for non-JSON-marshalable types (func, chan, complex)
  2. Replace unsupported values with JSON-safe equivalents (strings, numbers, slices, maps, marshalable structs)
  3. If marshaling keeps failing, replace map[string]any with a typed request struct so the compiler catches bad types

Example fix

// before
reqBody := map[string]any{
    "model":  o.Model,
    "input":  text,
    "voice":  voice,
}
// after
type speechRequest struct {
    Model string  `json:"model"`
    Input string  `json:"input"`
    Voice string  `json:"voice"`
    Speed float64 `json:"speed,omitempty"`
}
reqBody := speechRequest{Model: o.Model, Input: text, Voice: voice, Speed: opts.Speed}
Defensive patterns

Strategy: type-guard

Validate before calling

// Prefer a typed struct so the compiler rejects unmarshalable fields
type speechRequest struct {
    Model string  `json:"model"`
    Input string  `json:"input"`
    Voice string  `json:"voice"`
    Speed float64 `json:"speed,omitempty"`
}
body := speechRequest{Model: o.Model, Input: text, Voice: voice}
if _, err := json.Marshal(body); err != nil {
    return fmt.Errorf("request not serializable: %w", err)
}

Type guard

func jsonSafe(v any) bool {
    _, err := json.Marshal(v)
    return err == nil
}

Try / catch

audio, format, err := tts.Synthesize(ctx, text, opts)
if err != nil && strings.Contains(err.Error(), "marshal request") {
    return fmt.Errorf("tts request bug (non-serializable field): %w", err)
}

Prevention

When it happens

Trigger: Only if reqBody contains a value json.Marshal cannot encode — e.g. a future field added to the map holding a func, channel, or other unsupported type. With the current fields (model, input, voice, speed) it cannot occur.

Common situations: A developer modifying Synthesize to add an option (e.g. response_format, a nested struct with an unmarshalable field) introduces a non-serializable value into reqBody.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/208f134470cd2cad. Report an issue: GitHub.