{"record":{"id":"ac246218d3dea4ca","repo":"bytebase/bytebase","slug":"failed-to-read-body-of-s-s","errorCode":null,"errorMessage":"failed to read body of %s %s","messagePattern":"failed to read body of (.+?) (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/plugin/webhook/dingtalk/app.go","lineNumber":173,"sourceCode":"\t\t\t\treturn nil, false, errors.Wrapf(err, \"failed to construct %s %s\", method, url)\n\t\t\t}\n\n\t\t\treq.Header.Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\t\t\tif strings.HasPrefix(url, \"https://api.dingtalk.com\") {\n\t\t\t\treq.Header.Add(\"x-acs-dingtalk-access-token\", p.token)\n\t\t\t} else {\n\t\t\t\turl = url + \"?access_token=\" + p.token\n\t\t\t}\n\n\t\t\tresp, err := p.c.Do(req)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false, errors.Wrapf(err, \"%s %s\", method, url)\n\t\t\t}\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tb, err := io.ReadAll(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false, errors.Wrapf(err, \"failed to read body of %s %s\", method, url)\n\t\t\t}\n\n\t\t\tvar response struct {\n\t\t\t\tErrcode int    `json:\"errcode\"`\n\t\t\t\tErrmsg  string `json:\"errmsg\"`\n\n\t\t\t\tCode    string `json:\"code\"`\n\t\t\t\tMessage string `json:\"message\"`\n\t\t\t}\n\t\t\tif err := json.Unmarshal(b, &response); err != nil {\n\t\t\t\treturn nil, false, errors.Errorf(\"failed to unmarshal response\")\n\t\t\t}\n\t\t\tif response.Errcode == 88 || response.Code == \"InvalidAuthentication\" {\n\t\t\t\tif err := p.refreshToken(ctx); err != nil {\n\t\t\t\t\treturn nil, false, errors.Wrapf(err, \"failed to refresh token\")\n\t\t\t\t}\n\t\t\t\treturn nil, true, nil\n\t\t\t}","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/plugin/webhook/dingtalk/app.go#L155-L191","documentation":"This error occurs when the DingTalk webhook provider fails to read the HTTP response body after a successful request dispatch (io.ReadAll on resp.Body returned an error). The provider wraps the underlying I/O error with the HTTP method and URL so you know which DingTalk API call failed. It indicates a connection was established and a response started, but the body transfer broke mid-stream.","triggerScenarios":"Any call to provider.do() (used by getIDByPhone, sendMessage, getIDByEmail, getUserIDByEmail) where the response from https://oapi.dingtalk.com or https://api.dingtalk.com is truncated: connection reset by server, read timeout mid-body, proxy closing the connection, or TLS termination failure during body transfer.","commonSituations":"Corporate proxies or firewalls that kill long-running responses; network flakiness in containers/Kubernetes with aggressive connection idle timeouts; DingTalk API returning a chunked response that is interrupted; DNS-level hijacking devices that reset connections.","solutions":["Retry the request — the provider's do() loop handles token refresh retries but body-read errors fail immediately, so re-invoke the caller (e.g., re-run the webhook send).","Check network path to DingTalk: verify no proxy/firewall is truncating responses to oapi.dingtalk.com / api.dingtalk.com (curl the endpoint from the host).","Increase the http.Client timeout or configure a transport with sane idle-connection timeouts if a custom client was injected.","Inspect the wrapped underlying error (errors.Cause) to distinguish reset, timeout, or TLS failure and address accordingly."],"exampleFix":"// before: single-shot call\nb, err := p.do(ctx, http.MethodPost, url, payload)\nif err != nil { return err }\n// after: retry transient body-read failures\nvar b []byte\nfor i := 0; i < 3; i++ {\n    b, err = p.do(ctx, http.MethodPost, url, payload)\n    if err == nil { break }\n    if !isTransientNetErr(errors.Cause(err)) { return err }\n    time.Sleep(time.Duration(i+1) * time.Second)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var b []byte\nfor i := 0; i < 3; i++ {\n    b, err = p.do(ctx, http.MethodPost, url, payload)\n    if err == nil { break }\n    if !isTransient(errors.Cause(err)) { return err }\n    select {\n    case <-ctx.Done(): return ctx.Err()\n    case <-time.After(backoff(i)):\n    }\n}","preventionTips":["Deploy in a network with stable egress to api.dingtalk.com and oapi.dingtalk.com.","Set sensible http.Client/transport timeouts instead of unlimited waits.","Wrap webhook sends with an application-level retry with exponential backoff.","Monitor for transient network errors in logs and alert on sustained rates."],"tags":["network","http","io","dingtalk"],"backgroundTag":"network-request-failed","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}