chenhg5/cc-connect · error

qqbot: upload image: %w

Error message

qqbot: upload image: %w

What it means

After the reply-context assertion succeeds, SendImage() calls uploadRichMedia (file_type 1 = image) to get a file_info token from the QQ Bot rich-media API. Any failure of that HTTP upload is wrapped with this message and returned. The message was never sent; the error reflects the upload step, not the send step.

Source

Thrown at platform/qqbot/qqbot.go:243

	for _, chunk := range chunks {
		if err := p.sendMessage(rctx, chunk); err != nil {
			return err
		}
	}
	return nil
}

// SendImage uploads and sends an image via QQ Bot rich media API.
// Implements core.ImageSender.
func (p *Platform) SendImage(ctx context.Context, replyCtx any, img core.ImageAttachment) error {
	rctx, ok := replyCtx.(*replyContext)
	if !ok {
		return fmt.Errorf("qqbot: SendImage: invalid reply context type %T", replyCtx)
	}

	fileInfo, err := p.uploadRichMedia(rctx, 1, img.Data, "")
	if err != nil {
		return fmt.Errorf("qqbot: upload image: %w", err)
	}

	var url string
	switch rctx.messageType {
	case "group":
		url = fmt.Sprintf("%s/v2/groups/%s/messages", p.apiBase(), rctx.groupOpenID)
	case "c2c":
		url = fmt.Sprintf("%s/v2/users/%s/messages", p.apiBase(), rctx.userOpenID)
	default:
		return fmt.Errorf("qqbot: unknown message type %q", rctx.messageType)
	}

	body := map[string]any{
		"msg_type": 7,
		"media":    map[string]any{"file_info": fileInfo},
	}
	if rctx.eventMsgID != "" {
		body["msg_id"] = rctx.eventMsgID

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the wrapped inner error (%w) for the API's HTTP status and error code.
  2. Check the image size/format against QQ Bot rich-media limits before calling SendImage.
  3. Ensure the access token is fresh — the platform refreshes it, but long-lived processes may race expiry.
  4. Retry once after a short delay for transient 5xx/network errors.
  5. Verify the bot still has access to the target group/c2c conversation.

Example fix

// before
if err := p.SendImage(ctx, rctx, img); err != nil { return err }
// after
if err := p.SendImage(ctx, rctx, img); err != nil {
    if strings.Contains(err.Error(), "upload image") {
        slog.Warn("media upload failed, retrying", "error", err)
        time.Sleep(2 * time.Second)
        return p.SendImage(ctx, rctx, img)
    }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

if len(img.Data) == 0 { return errors.New("qqbot: refusing to send empty image") }
if len(img.Data) > maxMediaBytes { return fmt.Errorf("qqbot: image %d bytes exceeds limit", len(img.Data)) }

Try / catch

if err := p.SendImage(ctx, replyCtx, img); err != nil {
    if strings.Contains(err.Error(), "upload image") {
        var apiErr *APIError
        if errors.As(err, &apiErr) { /* inspect apiErr status/code before retry */ }
    }
}

Prevention

When it happens

Trigger: uploadRichMedia returns an error because the media API returned non-2xx, the base64 payload exceeds size limits, the access token expired mid-request, the media URL for the group/c2c type is wrong, or the network request failed.

Common situations: Image larger than QQ Bot's media size limit; expired access token not yet refreshed; QQ media endpoint transient 5xx; sending to a group the bot was removed from (permission error surfaced by the API).

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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