{"record":{"id":"6e0fc54292327e12","repo":"sipeed/picoclaw","slug":"read-response-w","errorCode":null,"errorMessage":"read response: %w","messagePattern":"read response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/model/online.go","lineNumber":57,"sourceCode":"\treq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"request failed: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, readErr := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\tif readErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"read error response: %w\", readErr)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"HTTP %d: %s\", resp.StatusCode, strings.TrimSpace(string(body)))\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read response: %w\", err)\n\t}\n\n\t// {\"data\": [...]} envelope. Distinguish \"envelope shape with empty list\"\n\t// from \"object without a data key\" via Data being non-nil after unmarshal:\n\t// json.Unmarshal sets Data to []modelEntry{} for `{\"data\":[]}` but leaves\n\t// it as nil when \"data\" is absent or null.\n\tvar envelope modelsAPIResponse\n\tif err := json.Unmarshal(body, &envelope); err == nil && envelope.Data != nil {\n\t\treturn envelope.Data, nil\n\t}\n\n\t// Bare-array shape, including `[]`.\n\tvar arr []modelEntry\n\tif err := json.Unmarshal(body, &arr); err == nil {\n\t\treturn arr, nil\n\t}\n\n\tpreview := body","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/model/online.go#L39-L75","documentation":"The request succeeded (200) but reading the full response body failed mid-stream while io.ReadAll drained it. The connection dropped after headers arrived, and the transport-level cause is wrapped.","triggerScenarios":"The server or a proxy closes the connection partway through the /models JSON body — RST mid-body, proxy idle cutoff, or the 15s client timeout expiring during a large body read at online.go:57.","commonSituations":"Very large model catalogs over slow links, flaky mobile/VPN networks, or intermediaries that truncate long responses.","solutions":["Retry — most mid-body drops are transient.","If reproducible, fetch the URL with curl and observe where the body truncates.","For huge catalogs, run from a nearer network or use a provider endpoint with pagination/filtering."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"body, err := io.ReadAll(resp.Body)\nif err != nil {\n    var nerr net.Error\n    if errors.As(err, &nerr) && nerr.Timeout() {\n        return retryWithBackoff() // body read exceeded client timeout\n    }\n    return fmt.Errorf(\"read response: %w\", err)\n}","preventionTips":["Retry once on mid-body failures — they are overwhelmingly transient.","For large catalogs, prefer providers/endpoints with pagination to keep bodies small."],"tags":["network","http","transient","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}