{"record":{"id":"38c0471d8b3c8ee1","repo":"JuliusBrussee/caveman","slug":"kms-read-s-response-w","errorCode":null,"errorMessage":"kms: read %s response: %w","messagePattern":"kms: read (.+?) response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/kms/kms.go","lineNumber":371,"sourceCode":"\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"kms: create %s request: %w\", operation, err)\n\t}\n\treq.Header.Set(\"content-type\", \"application/json\")\n\treq.Header.Set(\"accept\", \"application/json\")\n\treq.Header.Set(\"x-auth-token\", c.token)\n\tresp, err := c.httpClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"kms: %s request failed: %w\", operation, err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\t_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 32<<10))\n\t\treturn fmt.Errorf(\"kms: %s returned HTTP %d\", operation, resp.StatusCode)\n\t}\n\tdata, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes+1))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"kms: read %s response: %w\", operation, err)\n\t}\n\tif len(data) > maxResponseBytes {\n\t\treturn fmt.Errorf(\"kms: %s response exceeds limit\", operation)\n\t}\n\tif err := json.Unmarshal(data, output); err != nil {\n\t\treturn fmt.Errorf(\"kms: decode %s response: %w\", operation, err)\n\t}\n\treturn nil\n}\n","sourceCodeStart":353,"sourceCodeEnd":381,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/kms/kms.go#L353-L381","documentation":"After a 200 response, the helper reads the body with io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes+1)) (cap 512 KiB); a read error is wrapped as 'kms: read %s response: %w'. This means the connection broke mid-body: unexpected EOF, connection reset, or the context being cancelled while the body streamed. It is rare relative to 1197/1198 because headers already succeeded.","triggerScenarios":"Connection reset between response headers and body completion (LB idle timeout, proxy interruption); caller's context cancelled during body read; TLS session torn down mid-stream; oversized body exceeding the reader returning io.ErrUnexpectedEOF in edge cases.","commonSituations":"Load balancers with aggressive response timeouts cutting long KMS responses; mobile/unstable network edges; shared contexts cancelled by request handlers ending early; proxies buffering inconsistently.","solutions":["Read the wrapped error: 'unexpected EOF'/'connection reset' suggests intermediary timeouts; 'context canceled' means your ctx ended","Retry the operation — mid-body failures are characteristically transient","If it recurs, raise intermediary (LB/proxy) response timeouts on the KMS route","Avoid sharing a short-lived request context with KMS calls; derive one with its own deadline"],"exampleFix":"// before\nctx, cancel := context.WithTimeout(req.Context(), 300*time.Millisecond)\ndefer cancel()\npt, err := client.Encrypt(ctx, plaintext)\n\n// after\nctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\ndefer cancel()\npt, err := client.Encrypt(ctx, plaintext)","handlingStrategy":"retry","validationCode":"null","typeGuard":null,"tryCatchPattern":"pt, err := client.Encrypt(ctx, plaintext)\nif err != nil && strings.Contains(err.Error(), \"read \") && strings.Contains(err.Error(), \" response: \") {\n\tpt, err = client.Encrypt(ctx, plaintext) // mid-body read failures are transient; one retry\n}","preventionTips":["Derive KMS contexts with their own deadline instead of reusing request contexts","Raise LB/proxy response timeouts on routes to the KMS API","Retry once on mid-body read errors before surfacing to callers"],"tags":["go","kms","network","io"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}