chenhg5/cc-connect · error

tuitui: decode %s response: %w (body_len=%d)

Error message

tuitui: decode %s response: %w (body_len=%d)

What it means

When the caller passes a non-nil out, postJSON unmarshals the whole body directly into it. A body that is not valid JSON for the target type produces this wrapped error, including the endpoint and body length to aid debugging. It fires after the status-code check, so the status was 2xx but the payload didn't decode.

Source

Thrown at platform/tuitui/tuitui.go:737

	}
	defer func() { _ = resp.Body.Close() }()
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxJSONResponseBytes+1))
	if err != nil {
		return err
	}
	if len(body) > maxJSONResponseBytes {
		return fmt.Errorf("tuitui: %s response exceeds %d bytes", apiPath, maxJSONResponseBytes)
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("HTTP %d: %s", resp.StatusCode, body)
	}
	var apiResp struct {
		ErrCode int    `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
	}
	if out != nil {
		if err := json.Unmarshal(body, out); err != nil {
			return fmt.Errorf("tuitui: decode %s response: %w (body_len=%d)", apiPath, err, len(body))
		}
		return nil
	}
	if len(bytes.TrimSpace(body)) == 0 {
		return nil
	}
	if err := json.Unmarshal(body, &apiResp); err != nil {
		return fmt.Errorf("tuitui: decode %s response: %w (body_len=%d)", apiPath, err, len(body))
	}
	if apiResp.ErrCode != 0 {
		return fmt.Errorf("errcode=%d errmsg=%s", apiResp.ErrCode, apiResp.ErrMsg)
	}
	return nil
}

func (p *Platform) uploadMedia(ctx context.Context, data []byte, mimeType, filename, mediaType string) (mediaID, outName string, err error) {
	var body bytes.Buffer
	mw := multipart.NewWriter(&body)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Print/inspect the body (body_len hints at HTML page vs truncated JSON) at the failing endpoint.
  2. If the body is HTML from a proxy/WAF, fix the network path or whitelist the API host.
  3. Compare the actual JSON shape against the caller's out struct and update the struct fields.
  4. Retry the request — a transient truncation may succeed on retry.

Example fix

// before
var resp struct{ Items []Item `json:"data"` } // API actually returns {"result": [...]}
// after
var resp struct{ Result []Item `json:"result"` }
Defensive patterns

Strategy: try-catch

Validate before calling

resp, _ := http.Get(url)
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
    return fmt.Errorf("expected JSON, got %s", ct)
}

Try / catch

if err := p.postJSON(ctx, path, req, &out); err != nil {
    if strings.Contains(err.Error(), "decode") {
        slog.Warn("non-JSON 2xx body", "err", err) // inspect body_len
    }
    return err
}

Prevention

When it happens

Trigger: Endpoint returns 200 with an unexpected body for a typed decode: HTML error page from a proxy, truncated JSON, or a response shape that doesn't match the caller's out struct.

Common situations: Captive portal or WAF inserting HTML into 200 responses; Tuitui API response schema change; response truncated by an intermediary.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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