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.eventMsgIDView on GitHub (pinned to 4000b2338a)
Solutions
- Inspect the wrapped inner error (%w) for the API's HTTP status and error code.
- Check the image size/format against QQ Bot rich-media limits before calling SendImage.
- Ensure the access token is fresh — the platform refreshes it, but long-lived processes may race expiry.
- Retry once after a short delay for transient 5xx/network errors.
- 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
- Pre-check image size and format against QQ rich-media limits.
- Refresh the access token on auth-related upload failures.
- Add one retry with short backoff for transient 5xx from the media endpoint.
- Verify the bot's membership/permission in the target group before media sends.
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
- qqbot: api %s %s returned %d (after retry): %s
- qqbot: api %s %s returned %d: %s
- usage endpoint returned status %d: %s
- reasonix: POST %s returned %d: %s
- gitee API returned HTTP %d
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/b6bcfcb00c88ebbe.
Report an issue: GitHub.