{"record":{"id":"46b295b21a406b1d","repo":"chenhg5/cc-connect","slug":"s-status-d-s-close-body-v","errorCode":null,"errorMessage":"%s: status %d: %s (close body: %v)","messagePattern":"(.+?): status (.+?): (.+?) \\(close body: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/googlechat/googlechat.go","lineNumber":356,"sourceCode":"\t\treturn nil, fmt.Errorf(\"googlechat: invalid session key %q\", sessionKey)\n\t}\n\tif idx := strings.Index(rest, threadSep); idx != -1 {\n\t\treturn replyContext{space: rest[:idx], thread: rest[idx+len(threadSep):]}, nil\n\t}\n\t// User-scoped keys append \":<user>\" where user is \"users/<id>\"; strip a\n\t// trailing \"users/...\" segment to recover the bare space.\n\tif idx := strings.LastIndex(rest, \":users/\"); idx != -1 {\n\t\treturn replyContext{space: rest[:idx]}, nil\n\t}\n\treturn replyContext{space: rest}, nil\n}\n\n// httpErrorBody reads up to 2048 bytes from resp.Body, closes it, and returns\n// an error combining prefix, status code, and the response snippet.\nfunc httpErrorBody(resp *http.Response, prefix string) error {\n\tb, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))\n\tif err := resp.Body.Close(); err != nil {\n\t\treturn fmt.Errorf(\"%s: status %d: %s (close body: %v)\", prefix, resp.StatusCode, strings.TrimSpace(string(b)), err)\n\t}\n\treturn fmt.Errorf(\"%s: status %d: %s\", prefix, resp.StatusCode, strings.TrimSpace(string(b)))\n}\n\n// coalesce returns s if non-empty, otherwise def.\nfunc coalesce(s, def string) string {\n\tif s != \"\" {\n\t\treturn s\n\t}\n\treturn def\n}\n\n// messageURL returns the Chat messages endpoint for rc's space, appending the\n// messageReplyOption query when a thread is known.\nfunc messageURL(rc replyContext) string {\n\tu := chatAPIBase + rc.space + \"/messages\"\n\tif rc.thread != \"\" {\n\t\tu += \"?messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD\"","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/googlechat/googlechat.go#L338-L374","documentation":"httpErrorBody builds the error returned for non-2xx Google Chat API responses. It reads up to 2048 bytes of the response body for diagnostics; if closing the response body itself fails, the close error is appended in a '(close body: %v)' clause so neither detail is lost. This variant is rare and indicates an I/O problem while releasing the HTTP connection.","triggerScenarios":"doRequest receives a non-2xx response from the Chat API, and the subsequent resp.Body.Close() call fails (e.g. connection reset while closing, already-corrupted connection).","commonSituations":"Unstable networks or proxies interrupting keep-alive connections; HTTP client/connection pool issues; usually accompanied by a more meaningful status code and body snippet in the same message.","solutions":["Read the embedded status code and body snippet first — that is the real failure (e.g. 403 permission, 404 space) and fix that underlying API issue","Retry the request; body-close failures on keep-alive connections are typically transient","Disable aggressive HTTP keep-alive/proxy settings if close errors recur"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := send(); err != nil {\n    if strings.Contains(err.Error(), \"close body\") {\n        slog.Warn(\"chat api call failed and body close also failed; check status snippet\", \"err\", err)\n    }\n    return err\n}","preventionTips":["Focus on the embedded HTTP status and body snippet — the close-body clause is secondary","Retry transient failures; these usually stem from unstable connections","Audit proxy/keep-alive settings if close errors appear repeatedly"],"tags":["googlechat","http","api"],"backgroundTag":"http-error-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}