{"record":{"id":"af2e2f0fa5509f81","repo":"Tencent/WeKnora","slug":"empty-qqbot-access-token-code-d-message-s","errorCode":null,"errorMessage":"empty qqbot access token: code=%d message=%s","messagePattern":"empty qqbot access token: code=(.+?) message=(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/qqbot/client.go","lineNumber":195,"sourceCode":"func (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()\n\n\tbody := map[string]string{\n\t\t\"appId\":        c.appID,\n\t\t\"clientSecret\": c.clientSecret,\n\t}\n\tvar result tokenResponse\n\tif err := c.doJSON(ctx, http.MethodPost, appTokenURL, body, &result); err != nil {\n\t\treturn \"\", err\n\t}\n\tif result.AccessToken == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty qqbot access token: code=%d message=%s\", result.Code, result.Message)\n\t}\n\texpiresIn := parseExpiresIn(result.ExpiresIn)\n\n\tc.mu.Lock()\n\tc.accessToken = result.AccessToken\n\tc.expiresAt = time.Now().Add(time.Duration(expiresIn) * time.Second)\n\tc.mu.Unlock()\n\treturn result.AccessToken, nil\n}\n\nfunc parseExpiresIn(raw json.RawMessage) int {\n\tif len(raw) == 0 {\n\t\treturn 7200\n\t}\n\tvar number int\n\tif err := json.Unmarshal(raw, &number); err == nil && number > 0 {\n\t\treturn number\n\t}","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/qqbot/client.go#L177-L213","documentation":"After a successful token request, the QQ bot API responded with an empty access_token. The client treats this as a hard failure and includes the API's code and message fields, which usually indicate credential rejection.","triggerScenarios":"AccessToken POSTs appId/clientSecret to the app token endpoint and the response struct has AccessToken == \"\" — typically because credentials are wrong, the app is disabled, or the API returned an error payload with 2xx/4xx that still decoded.","commonSituations":"Misconfigured QQBOT_APPID / QQBOT_APPSECRET; app not yet approved or sandbox restrictions; wrong api_base_url routing to a mock returning empty tokens; app secret rotated server-side.","solutions":["Verify AppID and AppSecret in the channel credentials against the QQ developer console","Read the code/message fields in the error — they state the API's rejection reason","Confirm the app is approved/enabled and the correct api_base_url is used"],"exampleFix":"// before\ncreds := map[string]string{\"appId\": \"\", \"clientSecret\": \"\"}\n// after\ncreds := map[string]string{\"appId\": \"1020xxxxx\", \"clientSecret\": \"correct-secret-from-console\"}","handlingStrategy":"validation","validationCode":"if creds[\"appId\"] == \"\" || creds[\"clientSecret\"] == \"\" {\n    return errors.New(\"qqbot appId and clientSecret are required before requesting a token\")\n}","typeGuard":null,"tryCatchPattern":"token, err := client.AccessToken(ctx)\nif err != nil {\n    if strings.Contains(err.Error(), \"empty qqbot access token\") {\n        return fmt.Errorf(\"check AppID/AppSecret in QQ console: %w\", err)\n    }\n    return err\n}","preventionTips":["Verify AppID/AppSecret in the QQ developer console","Rotate secrets consistently on both sides","Confirm the app is approved and enabled","Use the correct api_base_url region"],"tags":["authentication","qqbot","credentials","access-token"],"backgroundTag":"invalid-api-credentials","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}