{"record":{"id":"0ab3638246994ec6","repo":"m1k1o/neko","slug":"unexpected-status-code-d-body-s","errorCode":null,"errorMessage":"unexpected status code: %d, body: %s","messagePattern":"unexpected status code: (.+?), body: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/http/legacy/session.go","lineNumber":107,"sourceCode":"\n\tres, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\tdefer res.Body.Close()\n\n\t\tbody, _ := io.ReadAll(res.Body)\n\t\t// try to unmarsal as json error message\n\t\tvar apiErr struct {\n\t\t\tMessage string `json:\"message\"`\n\t\t}\n\t\tif err := json.Unmarshal(body, &apiErr); err == nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"%w: %s\", ErrBackendRespone, apiErr.Message)\n\t\t}\n\t\t// return raw body if failed to unmarshal\n\t\treturn nil, nil, fmt.Errorf(\"unexpected status code: %d, body: %s\", res.StatusCode, strings.TrimSpace(string(body)))\n\t}\n\n\treturn res.Body, res.Header, nil\n}\n\nfunc (s *session) apiReq(method, path string, request, response any) error {\n\treqBody, err := json.Marshal(request)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\theaders := http.Header{\n\t\t\"Content-Type\": []string{\"application/json\"},\n\t}\n\n\tresBody, _, err := s.req(method, path, headers, bytes.NewReader(reqBody))\n\tif err != nil {\n\t\treturn err","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/m1k1o/neko/blob/b0f01cedea68893e85a3fd852c0521238c285695/server/internal/http/legacy/session.go#L89-L125","documentation":"This error is the fallback branch of session.req: the backend returned a non-success status code and the body could NOT be parsed as a JSON object with a 'message' field, so the raw status code and (trimmed) body are embedded in the error. It indicates an unexpected backend response format — often an HTML error page, empty body, or plain text from a proxy/load balancer rather than the application backend itself.","triggerScenarios":"apiReq hits a non-2xx response whose body is not JSON — e.g. an nginx 502 Bad Gateway HTML page, an empty 500 body, a Cloudflare block page, or a plain-text panic output.","commonSituations":"Backend process down behind a reverse proxy (502/504); TLS or routing misconfiguration landing on the wrong service; WAF/CDN intercepting requests; backend crash producing non-JSON output.","solutions":["Check the status code and body text embedded in the error to see which infrastructure layer answered.","Verify the backend process is running and reachable at the configured URL (curl the endpoint directly).","Inspect reverse proxy / load balancer logs between the proxy and backend.","Fix infrastructure issues or make the backend return structured JSON errors on failure paths."],"exampleFix":"// before\nif err := s.apiReq(\"GET\", \"/api/stats\", nil, &newStats); err != nil {\n    return err\n}\n// after\nif err := s.apiReq(\"GET\", \"/api/stats\", nil, &newStats); err != nil {\n    if strings.Contains(err.Error(), \"unexpected status code: 50\") {\n        return fmt.Errorf(\"backend unavailable, retry later: %w\", err)\n    }\n    return err\n}","handlingStrategy":"retry","validationCode":"resp, err := http.Get(backendURL + \"/api/stats\")\nif err != nil {\n    return fmt.Errorf(\"backend unreachable: %w\", err)\n}\nct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    return fmt.Errorf(\"backend answered non-JSON (%s); infrastructure issue likely\", ct)\n}","typeGuard":"func isNonJSONStatusError(err error) bool {\n    return strings.Contains(err.Error(), \"unexpected status code:\")\n}","tryCatchPattern":"err := s.apiReq(method, path, req, &out)\nif strings.Contains(err.Error(), \"unexpected status code: 50\") {\n    // transient infra failure: retry with backoff\n    time.Sleep(2 * time.Second)\n    err = s.apiReq(method, path, req, &out)\n}","preventionTips":["Health-check the backend (JSON endpoint) before starting API traffic.","Inspect reverse proxy/LB logs when non-JSON bodies appear.","Ensure the backend always returns JSON on error paths.","Distinguish 5xx (retry) from 4xx (fix request/auth) in handling."],"tags":["http","backend","non-json-response"],"backgroundTag":"unexpected-status-code","analyzedSha":"b0f01cedea68893e85a3fd852c0521238c285695","analyzedAt":"2026-09-01T10:35:56.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}