chenhg5/cc-connect · error

read upload response: %w

Error message

read upload response: %w

What it means

This error wraps an io.ReadAll failure that occurred while reading the body of the HTTP response from DingTalk's media upload endpoint. The upload request itself was sent successfully, but the response body could not be fully read, typically because the connection was interrupted or reset mid-transfer. It is thrown by the DingTalk platform's media upload helper (returning "", err) whenever the response body stream errors.

Source

Thrown at platform/dingtalk/dingtalk.go:1378

		return "", fmt.Errorf("close multipart writer: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadURL, body)
	if err != nil {
		return "", fmt.Errorf("create upload request: %w", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("upload request: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("read upload response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("upload returned status %d: %s", resp.StatusCode, respBody)
	}

	slog.Debug("dingtalk: media upload response", "status", resp.StatusCode, "body", string(respBody))

	var uploadResp struct {
		ErrCode int    `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
		MediaID string `json:"media_id"`
		Type    string `json:"type"`
	}
	if err := json.Unmarshal(respBody, &uploadResp); err != nil {
		return "", fmt.Errorf("decode upload response: %w, body: %s", err, respBody)
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check network connectivity and any corporate proxy/VPN between the host and DingTalk's API (oapi.dingtalk.com / api.dingtalk.com).
  2. Retry the send — the underlying error is often transient, so a subsequent upload attempt may succeed.
  3. Reduce the media file size or re-encode it so the response window is shorter.
  4. If a custom HTTP client/transport is used, verify read timeouts are not too aggressive for large uploads.
  5. Capture the wrapped error (%w) with errors.Unwrap or errors.Is to identify the exact net/http root cause (connection reset, EOF, timeout).

Example fix

// before
respBody, err := io.ReadAll(resp.Body)
if err != nil {
    return "", fmt.Errorf("read upload response: %w", err)
}
// after
respBody, err := io.ReadAll(resp.Body)
if err != nil {
    slog.Warn("dingtalk: media upload response read failed, will surface to caller", "err", err)
    return "", fmt.Errorf("read upload response: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// precondition check before upload
if len(data) == 0 {
    return fmt.Errorf("nothing to upload")
}
// ensure client has sane timeout
client := &http.Client{Timeout: 60 * time.Second}

Type guard

func isTransientReadErr(err error) bool {
    var ne net.Error
    return errors.As(err, &ne) || errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)
}

Try / catch

mediaID, err := uploadMedia(ctx, data, mediaType)
if err != nil {
    if isTransientReadErr(errors.Unwrap(err)) {
        // retry once after backoff
        time.Sleep(2 * time.Second)
        mediaID, err = uploadMedia(ctx, data, mediaType)
    }
    if err != nil {
        slog.Error("media upload failed reading response", "err", err)
        return err
    }
}

Prevention

When it happens

Trigger: Calling the media upload path (image/file/voice/video send) when io.ReadAll(resp.Body) returns a non-nil error — e.g. the TCP connection to DingTalk's upload host is closed or reset before the body completes, a proxy/VPN severs the stream, or a read deadline elapses partway through.

Common situations: Corporate proxies or firewalls killing long-running uploads; flaky mobile/office networks; very large media files whose download phase outlives a NAT or idle-timeout window; Docker/Kubernetes DNS or egress interruptions.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/d3aa0f336ae4a464. Report an issue: GitHub.