chenhg5/cc-connect · error

read resource: %w

Error message

read resource: %w

What it means

io.ReadAll on the resource response body failed while reading the file content in resourceSingleGet (wrapped as "read resource"). This is a mid-body I/O error: connection reset, unexpected EOF, context cancellation, or a proxy truncating the stream.

Source

Thrown at platform/feishu/resource_download.go:280

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

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4*1024))
		return nil, fmt.Errorf("resource API status=%d body=%q", resp.StatusCode, strings.TrimSpace(string(body)))
	}

	if cl := resp.ContentLength; cl > p.resourceMaxBytes {
		return nil, fmt.Errorf("resource too large: Content-Length=%d exceeds cap %d", cl, p.resourceMaxBytes)
	}

	// LimitReader caps the body too in case the server lies about
	// Content-Length; we read up to cap+1 bytes to detect the lie.
	data, err := io.ReadAll(io.LimitReader(resp.Body, p.resourceMaxBytes+1))
	if err != nil {
		return nil, fmt.Errorf("read resource: %w", err)
	}
	if int64(len(data)) > p.resourceMaxBytes {
		return nil, fmt.Errorf("resource too large: body exceeds cap %d", p.resourceMaxBytes)
	}
	slog.Debug(p.tag()+": resource downloaded (single GET)",
		"file_key", fileKey, "type", resType, "size", len(data))
	return data, nil
}

// resourceRangeChunk fetches a single byte range and verifies the
// Content-Range header agrees with what we asked for. Returns the bytes
// received; caller is responsible for ordering into the final buffer.
//
// Transient errors are retried with backoff; non-transient errors (4xx, 5xx
// other than the documented transient cases) fail fast.
func (p *Platform) resourceRangeChunk(
	ctx context.Context,
	token, messageID, fileKey, resType string,

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Retry the download — transient mid-body failures usually succeed on a second attempt
  2. Inspect the wrapped cause: context.DeadlineExceeded → increase ctx timeout; unexpected EOF → network/proxy issue
  3. Disable aggressive idle timeouts on proxies/LBs in front of egress, or raise their read timeout
  4. Ensure the HTTP client transport sets sensible timeouts (ResponseHeaderTimeout vs overall) so slow bodies aren't cut off
  5. For large files prefer the chunked path, which retries transient errors per chunk

Example fix

// before
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// after: allow time for large bodies
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
Defensive patterns

Strategy: retry

Try / catch

data, err := p.resourceSingleGet(ctx, token, msgID, fileKey, resType)
if err != nil {
    if strings.Contains(err.Error(), "read resource:") {
        return backoffRetry(ctx, 3, download) // transient mid-body IO error
    }
    return err
}

Prevention

When it happens

Trigger: resourceSingleGet got a 2xx response, passed the Content-Length check, but the body read failed — server closed the connection mid-transfer, ctx was cancelled, TLS/connection error mid-stream, or a proxy dropped the connection.

Common situations: Unstable network on long transfers; idle-connection timeouts or LB/proxy read timeouts cutting off a slow body; ctx deadline expiring while reading a large body; Feishu edge closing connections under load.

Related errors


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