{"record":{"id":"415f779220bfd874","repo":"Tencent/WeKnora","slug":"qqbot-api-s-s-failed-s","errorCode":null,"errorMessage":"qqbot api %s %s failed: %s","messagePattern":"qqbot api (.+?) (.+?) failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/qqbot/client.go","lineNumber":166,"sourceCode":"\t\treturn err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tif !strings.Contains(url, \"getAppAccessToken\") {\n\t\ttoken, err := c.AccessToken(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treq.Header.Set(\"Authorization\", \"QQBot \"+token)\n\t}\n\n\tresp, err := c.httpClient.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn fmt.Errorf(\"qqbot api %s %s failed: %s\", method, url, resp.Status)\n\t}\n\tif out == nil {\n\t\treturn nil\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(out); err != nil {\n\t\treturn fmt.Errorf(\"decode qqbot response: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *Client) AccessToken(ctx context.Context) (string, error) {\n\tc.mu.Lock()\n\tif c.accessToken != \"\" && time.Until(c.expiresAt) > time.Minute {\n\t\ttoken := c.accessToken\n\t\tc.mu.Unlock()\n\t\treturn token, nil\n\t}\n\tc.mu.Unlock()","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/qqbot/client.go#L148-L184","documentation":"doJSON treats any HTTP status outside 200-299 from the QQ bot API as a failure and surfaces method, URL, and the status line. This is the client's generic non-2xx API error path used by GatewayURL, sendText, and AccessToken.","triggerScenarios":"Any QQ bot API call (gateway discovery, sending a message, fetching an app access token) returns 4xx/5xx, e.g. 401 from an invalid/expired appID-token pair, 429 rate limit, or 5xx outage.","commonSituations":"Wrong AppID/AppSecret; token caching logic racing expiry; QQ API rate limiting; QQ platform outage; network proxy returning 502.","solutions":["Read the status code in the message: fix auth for 401 (check AppID/AppSecret), back off for 429, retry later for 5xx","Verify the access token is fresh and credentials are correct","Check QQ bot API status/incidents and add retry with backoff for transient codes"],"exampleFix":"// before\nclient.GatewayURL(ctx) // panics on 401 without handling\n// after\nu, err := client.GatewayURL(ctx)\nif err != nil {\n    var respErr *apiStatusError // wrap or parse status from message\n    log.Printf(\"qqbot api unavailable: %v\", err)\n    return retryWithBackoff(...)\n}","handlingStrategy":"retry","validationCode":"// pre-flight: verify credentials/token before message sends\nif _, err := client.AccessToken(ctx); err != nil {\n    return fmt.Errorf(\"credentials invalid, skipping send: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"u, err := client.GatewayURL(ctx)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed: 429\") {\n        return retryWithBackoff(ctx, func() error { _, err = client.GatewayURL(ctx); return err })\n    }\n    return err\n}","preventionTips":["Retry with exponential backoff on 5xx/429 only","Fail fast on 401 and re-check credentials","Monitor QQ API status for outages"],"tags":["http","api","qqbot","network"],"backgroundTag":"http-non-2xx-response","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}