CherryHQ/cherry-studio · error · Error
Invalid token response from QQ API: ${errorText}
Error message
Invalid token response from QQ API: ${errorText} What it means
Thrown when getAppAccessToken returns 2xx but the JSON body lacks access_token or expires_in. The whole response is JSON.stringify-d into the message as errorText. This catches a structurally-invalid success response rather than an HTTP failure.
Source
Thrown at src/main/ai/channels/adapters/qq/QqAdapter.ts:177
}
const response = await net.fetch('https://bots.qq.com/app/getAppAccessToken', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
appId: this.appId,
clientSecret: this.clientSecret
})
})
if (!response.ok) {
throw new Error(`Failed to get access token: HTTP ${response.status}`)
}
const data = (await response.json()) as { access_token?: string; expires_in?: number }
if (!data.access_token || !data.expires_in) {
const errorText = JSON.stringify(data)
throw new Error(`Invalid token response from QQ API: ${errorText}`)
}
this.tokenCache = {
accessToken: data.access_token,
expiresAt: Date.now() + data.expires_in * 1000
}
return data.access_token
}
private async apiRequest(
endpoint: string,
options?: { method?: string; body?: Record<string, unknown> }
): Promise<Response> {
const token = await this.getAccessToken()
const response = await net.fetch(endpoint, {
method: options?.method ?? 'GET',
headers: {View on GitHub (pinned to 726446b54c)
Solutions
- Read the embedded errorText to see QQ's actual 200-body shape (often an inline {code,message} error).
- If QQ returned an inline error, address the root cause (e.g. invalid clientSecret) — the HTTP status alone was misleading.
- If the schema changed, update the type cast and field reads to match the new response.
- Retry once for transient QQ inconsistencies.
Defensive patterns
Strategy: type-guard
Validate before calling
// Validate the parsed token shape before trusting it.
function isQqTokenShape(d: unknown): d is { access_token: string; expires_in: number } {
return typeof d === 'object' && d !== null
&& typeof (d as any).access_token === 'string'
&& typeof (d as any).expires_in === 'number'
} Type guard
function isQqInvalidTokenResponse(e: unknown): e is Error {
return e instanceof Error && e.message.startsWith('Invalid token response from QQ API:')
} Try / catch
try {
await qq.connect()
} catch (e) {
if (isQqInvalidTokenResponse(e)) { notifyUser('QQ returned an unexpected token response; retry later'); return }
throw e
} Prevention
- Treat a 200 body lacking token fields as an inline QQ error, not a transient blip.
- Keep the token-shape type guard in sync with QQ's documented response.
- Log the embedded errorText so schema drift is visible.
When it happens
Trigger: response.ok is true, data = await response.json(), but data.access_token or data.expires_in is falsy; the guard throws the stringified body.
Common situations: QQ API changed its token response schema, returned an error object inside a 200 body (e.g. {code, message} without the token fields), or a proxy returned 200 with an HTML/empty JSON payload.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Failed to get access token: HTTP ${response.status}
- Private key must be a non-empty string
- Invalid PEM format: missing BEGIN/END markers or key content
- Private key content is empty after cleaning
- Private key contains invalid characters (not valid Base64)
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/497ab751986fd407.
Report an issue: GitHub.