chenhg5/cc-connect · error

upload chunk %d: %w

Error message

upload chunk %d: %w

What it means

uploadWSMedia sends the file data to WeCom's AI Bot WebSocket API in base64-encoded chunks via 'aibot_upload_media_chunk' frames and waits for an ack for each chunk. This error wraps the failure of one of those per-chunk write/ack round trips (timeout, disconnected socket, or protocol error). The wrapped error identifies the underlying cause; the %d tells which chunk index failed.

Source

Thrown at platform/wecom/websocket_outbound_media.go:97

	for i := 0; i < totalChunks; i++ {
		start := i * wecomWSUploadChunkSize
		end := start + wecomWSUploadChunkSize
		if end > len(data) {
			end = len(data)
		}
		reqID := p.generateReqID("aibot_upload_media_chunk")
		chunkFrame := map[string]any{
			"cmd":     "aibot_upload_media_chunk",
			"headers": map[string]string{"req_id": reqID},
			"body": map[string]any{
				"upload_id":   initBody.UploadID,
				"chunk_index": i,
				"base64_data": base64.StdEncoding.EncodeToString(data[start:end]),
			},
		}
		if _, err := p.writeAndWaitFrameWithTimeout(ctx, chunkFrame, reqID, wsMediaAckTimeout); err != nil {
			return "", fmt.Errorf("upload chunk %d: %w", i, err)
		}
	}

	finishReqID := p.generateReqID("aibot_upload_media_finish")
	finishFrame := map[string]any{
		"cmd":     "aibot_upload_media_finish",
		"headers": map[string]string{"req_id": finishReqID},
		"body": map[string]any{
			"upload_id": initBody.UploadID,
		},
	}
	finishResp, err := p.writeAndWaitFrameWithTimeout(ctx, finishFrame, finishReqID, wsMediaAckTimeout)
	if err != nil {
		return "", fmt.Errorf("upload finish: %w", err)
	}
	var finishBody struct {
		MediaID string `json:"media_id"`
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check the wrapped cause: if it is a timeout, increase wsMediaAckTimeout or reduce chunk size for slow links
  2. Verify the WebSocket connection is alive before uploading (reconnect if needed) and retry the whole upload
  3. Confirm the WeCom AI Bot session/credentials are still valid — an expired session often fails mid-upload
  4. Log reqID and chunk index to correlate with WeCom server-side logs

Example fix

// before
if _, err := p.writeAndWaitFrameWithTimeout(ctx, chunkFrame, reqID, wsMediaAckTimeout); err != nil {
	return "", fmt.Errorf("upload chunk %d: %w", i, err)
}
// after
if _, err := p.writeAndWaitFrameWithTimeout(ctx, chunkFrame, reqID, wsMediaAckTimeout); err != nil {
	if p.conn == nil { // stale connection: reconnect once, then retry chunk
		if rerr := p.reconnect(ctx); rerr != nil {
			return "", fmt.Errorf("upload chunk %d: reconnect: %w", i, rerr)
		}
		return p.uploadWSMedia(ctx, mediaType, fileName, data)
	}
	return "", fmt.Errorf("upload chunk %d: %w", i, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if len(file.Data) == 0 || !p.IsConnected() {
	return fmt.Errorf("wecom-ws: not connected or empty data, skipping upload")
}

Try / catch

if err := p.SendFile(ctx, rc, file); err != nil {
	var uerr *uploadChunkError
	if errors.As(err, &uerr) && isTimeout(uerr.Unwrap()) {
		// reconnect and retry once
	}
	log.Warn("wecom file upload failed", "err", err)
}

Prevention

When it happens

Trigger: Called from SendImage or SendFile via uploadWSMedia when writeAndWaitFrameWithTimeout for chunk frame i returns an error: WS connection closed, ack not received within wsMediaAckTimeout, or the frame write failed.

Common situations: WebSocket dropped mid-upload (network blip, WeCom server restart); large files whose upload takes longer than the per-chunk ack timeout; token/session expiry invalidating the WS session; concurrent requests over the same WS connection desyncing request IDs.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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