{"record":{"id":"d281d01a2bf2961e","repo":"wtfutil/wtf","slug":"decoding-response-w","errorCode":null,"errorMessage":"decoding response: %w","messagePattern":"decoding response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/devto/client.go","lineNumber":79,"sourceCode":"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"creating request: %w\", err)\n\t}\n\n\tresp, err := c.httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"executing request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"unexpected status %d from DEV.to API\", resp.StatusCode)\n\t}\n\n\tvar articles []Article\n\tif err := json.NewDecoder(resp.Body).Decode(&articles); err != nil {\n\t\treturn nil, fmt.Errorf(\"decoding response: %w\", err)\n\t}\n\n\treturn articles, nil\n}\n","sourceCodeStart":61,"sourceCodeEnd":84,"githubUrl":"https://github.com/wtfutil/wtf/blob/bb838c1ccb0f0f3223690df44afdec663d622881/modules/devto/client.go#L61-L84","documentation":"After a 200 OK, FetchArticles decodes the response body into []Article with json.NewDecoder(...).Decode. If the body is not a JSON array matching the Article struct (or is empty/truncated), the decode error is wrapped as \"decoding response: %w\". This typically means the API answered 200 but did not return the expected article JSON.","triggerScenarios":"DEV.to returns 200 with an HTML page (proxy/captive portal), an empty body, or a JSON shape that fails unmarshal into []Article (e.g., an object instead of an array, or incompatible field types).","commonSituations":"Corporate proxies intercepting TLS, DEV.to serving a maintenance page with 200, API version change altering the articles payload, or truncated responses on flaky connections.","solutions":["Log the wrapped error (%v) and the first bytes of the body to see what was actually returned.","Confirm the request went directly to https://dev.to/api/articles and is not intercepted by a proxy.","Compare the payload against the Article struct fields; update the struct if the API shape changed.","Retry on transient truncation; treat persistent mismatch as an API contract change."],"exampleFix":"// before\nif err := json.NewDecoder(resp.Body).Decode(&articles); err != nil {\n\treturn nil, fmt.Errorf(\"decoding response: %w\", err)\n}\n// after\nbody, err := io.ReadAll(resp.Body)\nif err != nil {\n\treturn nil, fmt.Errorf(\"reading response: %w\", err)\n}\nif err := json.Unmarshal(body, &articles); err != nil {\n\treturn nil, fmt.Errorf(\"decoding response %q: %w\", truncate(body, 200), err)\n}","handlingStrategy":"try-catch","validationCode":"// Peek content-type before decoding\nif ct := resp.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"application/json\") {\n\treturn fmt.Errorf(\"expected JSON, got Content-Type %q\", ct)\n}","typeGuard":"func asDecodeError(err error) (*json.SyntaxError, bool) {\n\tvar se *json.SyntaxError\n\tif errors.As(err, &se) {\n\t\treturn se, true\n\t}\n\treturn nil, false\n}","tryCatchPattern":"articles, err := client.FetchArticles(ctx)\nif err != nil {\n\tvar se *json.SyntaxError\n\tif errors.As(err, &se) {\n\t\tlog.Printf(\"non-JSON body from DEV.to at offset %d: %v\", se.Offset, err)\n\t\treturn\n\t}\n\tlog.Printf(\"devto fetch failed: %v\", err)\n}","preventionTips":["Check Content-Type of upstream responses before decoding.","Capture a snippet of failed bodies for diagnosis (limit read size).","Pin/update the Article struct when the DEV.to API schema changes."],"tags":["json","decoding","api","go"],"backgroundTag":"invalid-json-response","analyzedSha":"bb838c1ccb0f0f3223690df44afdec663d622881","analyzedAt":"2026-09-03T17:02:45.030Z","contentChangedAt":"2026-09-03T17:02:45.030Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}