chenhg5/cc-connect · error

read response body: %w

Error message

read response body: %w

What it means

downloadSlackFile fetches a Slack file via its private/public URL and reads the full response body with io.ReadAll. This error wraps any transient I/O failure that occurs while streaming the body (connection reset, timeout, context cancellation mid-read). It indicates the download failed partway through after a successful HTTP 200.

Source

Thrown at platform/slack/slack.go:582

		return nil, fmt.Errorf("empty URL")
	}
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+p.botToken)
	resp, err := core.HTTPClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("%s", core.RedactToken(err.Error(), p.botToken))
	}
	defer resp.Body.Close()

	// Check if we got an unexpected status code (e.g., redirect to login page)
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
		return nil, fmt.Errorf("download failed with status %d: %s", resp.StatusCode, string(body))
	}

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

	// Basic sanity check: detect if we received HTML instead of binary data
	if len(data) > 0 && (bytes.HasPrefix(data, []byte("<!DOCTYPE")) || bytes.HasPrefix(data, []byte("<html"))) {
		return nil, fmt.Errorf("received HTML response (likely missing auth); first 100 bytes: %s", string(data[:min(100, len(data))]))
	}

	return data, nil
}

func (p *Platform) ReconstructReplyCtx(sessionKey string) (any, error) {
	// slack:{channel}:{user}  |  slack:{channel}:t:{threadTS}  |  slack:{channel}
	parts := strings.SplitN(sessionKey, ":", 3)
	if len(parts) < 2 || parts[0] != "slack" {
		return nil, fmt.Errorf("slack: invalid session key %q", sessionKey)
	}
	rc := replyContext{channel: parts[1]}
	// Thread-scoped keys carry the thread root ts as a "t:<ts>" suffix; preserve

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Retry downloadSlackFile with backoff; body-read failures are usually transient
  2. Check network connectivity / proxy configuration between the host and Slack CDN
  3. Ensure the context passed down is not being cancelled prematurely (e.g. short engine timeout)
  4. Log the wrapped error to identify whether it is a timeout, reset, or cancellation

Example fix

// before
data, err := io.ReadAll(resp.Body)
if err != nil {
    return nil, fmt.Errorf("read response body: %w", err)
}
// after
data, err := io.ReadAll(resp.Body)
if err != nil {
    return nil, fmt.Errorf("read response body: %w", err) // caller should retry with backoff
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-check not possible for mid-read failure; instead bound it:
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
req = req.WithContext(ctx)

Try / catch

data, err := downloadSlackFile(url, token)
if err != nil {
    if strings.Contains(err.Error(), "read response body") {
        // transient: retry with backoff
    }
    return fmt.Errorf("process file share: %w", err)
}

Prevention

When it happens

Trigger: resp.Body read fails inside downloadSlackFile after the status-200 check — e.g. server closes the connection mid-transfer, network drops, or the request context is cancelled while reading.

Common situations: Large Slack file downloads over flaky networks; Slack CDN closing idle connections; bot shutdown cancelling the context during processSlackFileShares; proxy/VPN interruptions.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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