bytebase/bytebase · error

failed to read body of %s %s

Error message

failed to read body of %s %s

What it means

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.

Source

Thrown at backend/plugin/webhook/dingtalk/app.go:173

				return nil, false, errors.Wrapf(err, "failed to construct %s %s", method, url)
			}

			req.Header.Set("Content-Type", "application/json; charset=utf-8")
			if strings.HasPrefix(url, "https://api.dingtalk.com") {
				req.Header.Add("x-acs-dingtalk-access-token", p.token)
			} else {
				url = url + "?access_token=" + p.token
			}

			resp, err := p.c.Do(req)
			if err != nil {
				return nil, false, errors.Wrapf(err, "%s %s", method, url)
			}
			defer resp.Body.Close()

			b, err := io.ReadAll(resp.Body)
			if err != nil {
				return nil, false, errors.Wrapf(err, "failed to read body of %s %s", method, url)
			}

			var response struct {
				Errcode int    `json:"errcode"`
				Errmsg  string `json:"errmsg"`

				Code    string `json:"code"`
				Message string `json:"message"`
			}
			if err := json.Unmarshal(b, &response); err != nil {
				return nil, false, errors.Errorf("failed to unmarshal response")
			}
			if response.Errcode == 88 || response.Code == "InvalidAuthentication" {
				if err := p.refreshToken(ctx); err != nil {
					return nil, false, errors.Wrapf(err, "failed to refresh token")
				}
				return nil, true, nil
			}

View on GitHub (pinned to 1870550677)

Solutions

  1. 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).
  2. 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).
  3. Increase the http.Client timeout or configure a transport with sane idle-connection timeouts if a custom client was injected.
  4. Inspect the wrapped underlying error (errors.Cause) to distinguish reset, timeout, or TLS failure and address accordingly.

Example fix

// before: single-shot call
b, err := p.do(ctx, http.MethodPost, url, payload)
if err != nil { return err }
// after: retry transient body-read failures
var b []byte
for i := 0; i < 3; i++ {
    b, err = p.do(ctx, http.MethodPost, url, payload)
    if err == nil { break }
    if !isTransientNetErr(errors.Cause(err)) { return err }
    time.Sleep(time.Duration(i+1) * time.Second)
}
Defensive patterns

Strategy: retry

Try / catch

var b []byte
for i := 0; i < 3; i++ {
    b, err = p.do(ctx, http.MethodPost, url, payload)
    if err == nil { break }
    if !isTransient(errors.Cause(err)) { return err }
    select {
    case <-ctx.Done(): return ctx.Err()
    case <-time.After(backoff(i)):
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06). Data as JSON: /api/errors/ac246218d3dea4ca. Report an issue: GitHub.