{"record":{"id":"3c8f64eef1d68e1d","repo":"chenhg5/cc-connect","slug":"tuitui-decode-s-response-w-body-len-d","errorCode":null,"errorMessage":"tuitui: decode %s response: %w (body_len=%d)","messagePattern":"tuitui: decode (.+?) response: %w \\(body_len=(.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/tuitui/tuitui.go","lineNumber":737,"sourceCode":"\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxJSONResponseBytes+1))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(body) > maxJSONResponseBytes {\n\t\treturn fmt.Errorf(\"tuitui: %s response exceeds %d bytes\", apiPath, maxJSONResponseBytes)\n\t}\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn fmt.Errorf(\"HTTP %d: %s\", resp.StatusCode, body)\n\t}\n\tvar apiResp struct {\n\t\tErrCode int    `json:\"errcode\"`\n\t\tErrMsg  string `json:\"errmsg\"`\n\t}\n\tif out != nil {\n\t\tif err := json.Unmarshal(body, out); err != nil {\n\t\t\treturn fmt.Errorf(\"tuitui: decode %s response: %w (body_len=%d)\", apiPath, err, len(body))\n\t\t}\n\t\treturn nil\n\t}\n\tif len(bytes.TrimSpace(body)) == 0 {\n\t\treturn nil\n\t}\n\tif err := json.Unmarshal(body, &apiResp); err != nil {\n\t\treturn fmt.Errorf(\"tuitui: decode %s response: %w (body_len=%d)\", apiPath, err, len(body))\n\t}\n\tif apiResp.ErrCode != 0 {\n\t\treturn fmt.Errorf(\"errcode=%d errmsg=%s\", apiResp.ErrCode, apiResp.ErrMsg)\n\t}\n\treturn nil\n}\n\nfunc (p *Platform) uploadMedia(ctx context.Context, data []byte, mimeType, filename, mediaType string) (mediaID, outName string, err error) {\n\tvar body bytes.Buffer\n\tmw := multipart.NewWriter(&body)","sourceCodeStart":719,"sourceCodeEnd":755,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/tuitui/tuitui.go#L719-L755","documentation":"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.","triggerScenarios":"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.","commonSituations":"Captive portal or WAF inserting HTML into 200 responses; Tuitui API response schema change; response truncated by an intermediary.","solutions":["Print/inspect the body (body_len hints at HTML page vs truncated JSON) at the failing endpoint.","If the body is HTML from a proxy/WAF, fix the network path or whitelist the API host.","Compare the actual JSON shape against the caller's out struct and update the struct fields.","Retry the request — a transient truncation may succeed on retry."],"exampleFix":"// before\nvar resp struct{ Items []Item `json:\"data\"` } // API actually returns {\"result\": [...]}\n// after\nvar resp struct{ Result []Item `json:\"result\"` }","handlingStrategy":"try-catch","validationCode":"resp, _ := http.Get(url)\nct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    return fmt.Errorf(\"expected JSON, got %s\", ct)\n}","typeGuard":null,"tryCatchPattern":"if err := p.postJSON(ctx, path, req, &out); err != nil {\n    if strings.Contains(err.Error(), \"decode\") {\n        slog.Warn(\"non-JSON 2xx body\", \"err\", err) // inspect body_len\n    }\n    return err\n}","preventionTips":["Check Content-Type before JSON decoding when calling APIs manually","Watch for proxies/WAFs rewriting 200 responses to HTML","Keep out structs in sync with the Tuitui API schema"],"tags":["json","http","tuitui"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}