{"record":{"id":"7408ba0d0dda817b","repo":"zeroclaw-labs/zeroclaw","slug":"qq-token-request-failed-status-err","errorCode":null,"errorMessage":"QQ token request failed ({status}): {err}","messagePattern":"QQ token request failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":430,"sourceCode":"\n    /// Fetch an access token from QQ's OAuth2 endpoint.\n    async fn fetch_access_token(&self) -> anyhow::Result<(String, u64)> {\n        let body = json!({\n            \"appId\": self.app_id,\n            \"clientSecret\": self.app_secret,\n        });\n\n        let resp = self\n            .http_client()\n            .post(QQ_AUTH_URL)\n            .json(&body)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let err = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"QQ token request failed ({status}): {err}\");\n        }\n\n        let data: serde_json::Value = resp.json().await?;\n        let token = data\n            .get(\"access_token\")\n            .and_then(|t| t.as_str())\n            .ok_or_else(|| {\n                ::zeroclaw_log::record!(\n                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Failure),\n                    \"Missing access_token in QQ response\"\n                );\n                anyhow::Error::msg(\"Missing access_token in QQ response\")\n            })?\n            .to_string();\n\n        let expires_in = data","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L412-L448","documentation":"fetch_access_token POSTs {\"appId\", \"clientSecret\"} to https://bots.qq.com/app/getAppAccessToken; any non-2xx response bails with the HTTP status and response body. The caller fetch_access_token_with_retry already retries with jittered exponential backoff up to AUTH_RETRY_MAX_ATTEMPTS, so this error escaping means every attempt failed — most commonly invalid credentials or a blocked network path.","triggerScenarios":"Wrong app_id or app_secret in [channels.qq.<alias>]; the QQ open platform rejecting/ suspending the app (body usually says so); egress firewall or a broken channel.qq proxy_url breaking the POST to bots.qq.com.","commonSituations":"Rotated QQ app secrets not updated in config; sandbox vs production app credentials mixed up; environments without direct outbound access to bots.qq.com; copying app_id from the QQ console with whitespace/quotes.","solutions":["Read the status and body in the message: 4xx means credentials/app status — fix app_id/app_secret in [channels.qq.<alias>]; 5xx means QQ-side, retry later","Verify outbound connectivity to https://bots.qq.com (curl) and check any channel.qq proxy_url setting","After fixing credentials, let the built-in retry handle the rest; no code change needed for transient failures"],"exampleFix":"# before\n[channels.qq.main]\napp_id = \"102030405\"\napp_secret = \"stale-secret\"        # -> QQ token request failed (401 Unauthorized)\n\n# after\n[channels.qq.main]\napp_id = \"102030405\"\napp_secret = \"current-secret-from-qq-open-platform\"","handlingStrategy":"retry","validationCode":"// Fail fast on missing/obviously-wrong credentials before the first token call:\nfn qq_credentials_present(cfg: &Config, alias: &str) -> anyhow::Result<()> {\n    let qq = cfg.channels.qq.get(alias).context(\"[channels.qq.{alias}] not configured\")?;\n    anyhow::ensure!(!qq.app_id.trim().is_empty(), \"qq app_id is empty\");\n    anyhow::ensure!(!qq.app_secret.trim().is_empty(), \"qq app_secret is empty\");\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"// The library already retries with jittered backoff; only handle terminal failure:\nmatch qq.listen().await {\n    Err(e) if e.to_string().contains(\"QQ token request failed\") => {\n        let body = e.to_string();\n        if body.contains(\"401\") || body.contains(\"400\") {\n            // Credential problem: stop retrying, alert operator to fix app_id/app_secret.\n        } else {\n            // 5xx/platform issue: restart the channel later (external backoff).\n        }\n    }\n    rest => rest?,\n}","preventionTips":["Store QQ secrets via the secret store, not inline, so rotations propagate","Monitor for this error after every QQ open-platform credential rotation","Check egress/proxy_url reachability to https://bots.qq.com when deploying in restricted networks"],"tags":["qq","oauth","access-token","authentication","network"],"backgroundTag":"oauth-token-request-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}