{"record":{"id":"b4f100ff558a1a65","repo":"charmbracelet/crush","slug":"failed-to-unmarshal-response-w","errorCode":null,"errorMessage":"failed to unmarshal response: %w","messagePattern":"failed to unmarshal response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/sourcegraph.go","lineNumber":144,"sourceCode":"\t\t\t}\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\tbody, _ := io.ReadAll(resp.Body)\n\t\t\t\tif len(body) > 0 {\n\t\t\t\t\treturn fantasy.NewTextErrorResponse(fmt.Sprintf(\"Request failed with status code: %d, response: %s\", resp.StatusCode, string(body))), nil\n\t\t\t\t}\n\n\t\t\t\treturn fantasy.NewTextErrorResponse(fmt.Sprintf(\"Request failed with status code: %d\", resp.StatusCode)), nil\n\t\t\t}\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to read response body: %w\", err)\n\t\t\t}\n\n\t\t\tvar result map[string]any\n\t\t\tif err = json.Unmarshal(body, &result); err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to unmarshal response: %w\", err)\n\t\t\t}\n\n\t\t\tformattedResults, err := formatSourcegraphResults(result, params.ContextWindow, params.Count)\n\t\t\tif err != nil {\n\t\t\t\treturn fantasy.NewTextErrorResponse(\"Failed to format results: \" + err.Error()), nil\n\t\t\t}\n\n\t\t\treturn fantasy.NewTextResponse(formattedResults), nil\n\t\t},\n\t)\n}\n\nfunc formatSourcegraphResults(result map[string]any, contextWindow, maxResults int) (string, error) {\n\tvar buffer strings.Builder\n\n\tif writeSourcegraphErrors(&buffer, result) {\n\t\treturn buffer.String(), nil\n\t}","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/sourcegraph.go#L126-L162","documentation":"Wraps a json.Unmarshal error when decoding the Sourcegraph GraphQL JSON response into map[string]any (internal/agent/tools/sourcegraph.go:143-145). The HTTP response was 200 but the body is not valid JSON — e.g. an HTML error page, an empty body, or a truncated payload. A 200 with non-JSON content usually indicates an intermediary (proxy, auth wall) intercepting the response.","triggerScenarios":"json.Unmarshal(body, &result) fails: response body is empty, truncated mid-stream (still yields invalid JSON), HTML from a proxy/captive portal served with status 200, or a WAF challenge page.","commonSituations":"Corporate proxies returning 200 HTML login pages; a load balancer serving an empty 200 during Sourcegraph incidents; connection truncation producing partial JSON; misconfigured local proxies (mitmproxy, Charles) mangling the body.","solutions":["Log the first bytes of body on failure to see whether it is HTML, empty, or truncated JSON.","Check for a proxy/auth wall intercepting sourcegraph.com and bypass or authenticate it.","Retry the request; a single truncation is usually transient.","Consider validating Content-Type is application/json before unmarshalling to produce a clearer error."],"exampleFix":"// before\nvar result map[string]any\nif err = json.Unmarshal(body, &result); err != nil {\n    return fantasy.ToolResponse{}, fmt.Errorf(\"failed to unmarshal response: %w\", err)\n}\n\n// after\nvar result map[string]any\nif err = json.Unmarshal(body, &result); err != nil {\n    return fantasy.ToolResponse{}, fmt.Errorf(\"failed to unmarshal response: %w (body: %.200q)\", err, body)\n}","handlingStrategy":"validation","validationCode":"if ct := resp.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"application/json\") {\n    return fmt.Errorf(\"expected JSON response, got Content-Type: %q\", ct)\n}\nif len(body) == 0 {\n    return fmt.Errorf(\"empty response body with status 200\")\n}","typeGuard":null,"tryCatchPattern":"var result map[string]any\nif err := json.Unmarshal(body, &result); err != nil {\n    log.Printf(\"invalid JSON from sourcegraph, head: %.200q\", body)\n    return fmt.Errorf(\"failed to unmarshal response: %w\", err)\n}","preventionTips":["Check Content-Type and body prefix before unmarshalling to detect HTML/WAF pages.","Watch for proxies or auth walls that answer 200 with non-JSON bodies.","Retry once on truncation; it is usually transient.","Log body heads on failure — never the whole body — for fast diagnosis."],"tags":["json","http","parsing"],"backgroundTag":"invalid-json-response","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}