zeroclaw-labs/zeroclaw · error
QQ token request failed ({status}): {err}
Error message
QQ token request failed ({status}): {err} What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/qq.rs:430
/// Fetch an access token from QQ's OAuth2 endpoint.
async fn fetch_access_token(&self) -> anyhow::Result<(String, u64)> {
let body = json!({
"appId": self.app_id,
"clientSecret": self.app_secret,
});
let resp = self
.http_client()
.post(QQ_AUTH_URL)
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
let err = resp.text().await.unwrap_or_default();
anyhow::bail!("QQ token request failed ({status}): {err}");
}
let data: serde_json::Value = resp.json().await?;
let token = data
.get("access_token")
.and_then(|t| t.as_str())
.ok_or_else(|| {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
.with_outcome(::zeroclaw_log::EventOutcome::Failure),
"Missing access_token in QQ response"
);
anyhow::Error::msg("Missing access_token in QQ response")
})?
.to_string();
let expires_in = dataView on GitHub (pinned to 88bb9c8533)
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
Example fix
# before [channels.qq.main] app_id = "102030405" app_secret = "stale-secret" # -> QQ token request failed (401 Unauthorized) # after [channels.qq.main] app_id = "102030405" app_secret = "current-secret-from-qq-open-platform"
Defensive patterns
Strategy: retry
Validate before calling
// Fail fast on missing/obviously-wrong credentials before the first token call:
fn qq_credentials_present(cfg: &Config, alias: &str) -> anyhow::Result<()> {
let qq = cfg.channels.qq.get(alias).context("[channels.qq.{alias}] not configured")?;
anyhow::ensure!(!qq.app_id.trim().is_empty(), "qq app_id is empty");
anyhow::ensure!(!qq.app_secret.trim().is_empty(), "qq app_secret is empty");
Ok(())
} Try / catch
// The library already retries with jittered backoff; only handle terminal failure:
match qq.listen().await {
Err(e) if e.to_string().contains("QQ token request failed") => {
let body = e.to_string();
if body.contains("401") || body.contains("400") {
// Credential problem: stop retrying, alert operator to fix app_id/app_secret.
} else {
// 5xx/platform issue: restart the channel later (external backoff).
}
}
rest => rest?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- QQ gateway request failed ({status}): {err}
- QQ send message failed ({status}): {err}
- QQ WebSocket connection closed: stream ended unexpectedly
- QQ WebSocket connection closed: heartbeat ACK timeout ({MAX_
- QQ WebSocket connection closed: write failed
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/7408ba0d0dda817b.
Report an issue: GitHub.