chenhg5/cc-connect · error
qqbot: api %s %s returned %d (after retry): %s
Error message
qqbot: api %s %s returned %d (after retry): %s
What it means
After the 401 retry, if the second response still has an HTTP status >= 300, apiRequestJSON returns this error including the method, URL, status code, and the raw response body. It means the token refresh did not fix the authorization problem, or a non-auth error (400/403/404/413/429) occurred on the retried call.
Source
Thrown at platform/qqbot/qqbot.go:358
data, _ := json.Marshal(body)
bodyReader = bytes.NewReader(data)
}
req2, err := http.NewRequest(method, url, bodyReader)
if err != nil {
return fmt.Errorf("qqbot: build retry request: %w", err)
}
req2.Header.Set("Authorization", "QQBot "+token)
req2.Header.Set("Content-Type", "application/json")
resp2, err := core.HTTPClient.Do(req2)
if err != nil {
return fmt.Errorf("qqbot: api retry failed: %w", err)
}
defer resp2.Body.Close()
if resp2.StatusCode >= 300 {
raw, _ := io.ReadAll(resp2.Body)
return fmt.Errorf("qqbot: api %s %s returned %d (after retry): %s", method, url, resp2.StatusCode, raw)
}
if result != nil {
if err := json.NewDecoder(resp2.Body).Decode(result); err != nil {
return fmt.Errorf("qqbot: decode response: %w", err)
}
}
return nil
}
if resp.StatusCode >= 300 {
raw, _ := io.ReadAll(resp.Body)
return fmt.Errorf("qqbot: api %s %s returned %d: %s", method, url, resp.StatusCode, raw)
}
if result != nil {
if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
return fmt.Errorf("qqbot: decode response: %w", err)
}
}View on GitHub (pinned to 4000b2338a)
Solutions
- Read the raw body in the error message — the QQ API error code explains the cause (e.g. 11253 rate limit, invalid openID).
- Still 401 after retry: re-verify appId/appSecret in config.toml; restart to force fresh tokens.
- 403/404: confirm the bot is a member of the target group and the openID matches an existing chat.
- 413: reduce file size to QQ Bot rich media limits before uploading.
- 429: back off and retry later; check nextMsgSeq/msg_id reuse.
Defensive patterns
Strategy: try-catch
Validate before calling
if len(fileData) > maxQQBotFileSize {
return fmt.Errorf("file %d bytes exceeds qqbot limit", len(fileData))
}
if groupOpenID == "" {
return fmt.Errorf("cannot send: empty group openID")
} Try / catch
var apiErr struct{ Code int `json:"code"`; Message string `json:"message"` }
if m := reBody.FindStringSubmatch(err.Error()); m != nil {
_ = json.Unmarshal([]byte(m[1]), &apiErr) // parse raw body from the error text
switch apiErr.Code {
case rateLimitedCode:
// backoff and retry
default:
slog.Error("qqbot api error after retry", "code", apiErr.Code, "msg", apiErr.Message)
}
} Prevention
- Stay under QQ rich media size limits before calling SendImage/SendFile.
- Verify the bot is a member of the target group before sending.
- Increment msg_seq per msg_id to avoid dedup rejections.
- Back off on rate-limit codes surfaced in the response body.
When it happens
Trigger: The first attempt got 401, refresh + retry succeeded at transport level, but resp2.StatusCode >= 300 — e.g. still 401 (bad credentials), 403 (no permission for that group/user), 404 (bad openID), 413 (file too large), or 429 (rate limited).
Common situations: Wrong or revoked appSecret surviving a refresh; bot not added to the target group; invalid file_type/file size on rich media upload; exceeding QQ message rate limits.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- qqbot: upload image: %w
- 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/2485a81c9688b199.
Report an issue: GitHub.