CherryHQ/cherry-studio · error · Error

QQ API request failed

Error message

QQ API request failed ${endpoint}: HTTP ${response.status} - ${errorText}

What it means

Generic gateway thrown by QqAdapter.apiRequest() after net.fetch to a QQ API endpoint returns non-2xx. Embeds endpoint, status, and the raw errorText (with a .catch fallback to ''). Used by gateway lookup, message send (sendToChat), and other QQ REST calls.

Solutions

  1. On 401, invalidate this.tokenCache and call fetchAccessToken() once, then retry the original request (the cache has expiresAt but a server-side revocation can preempt it).
  2. Read the embedded errorText for QQ's {code, message,...} to find the specific cause (e.g. permission scope missing).
  3. For 429, honor Retry-After / back off.
  4. Verify X-Union-Appid equals this.appId and the endpoint path matches the chat type.
Defensive patterns

Strategy: retry

Validate before calling

// Pre-send: ensure the token cache is fresh.
if (!qq.tokenFresh()) await qq.refreshToken()
await qq.sendToChat(chatId, text)

Type guard

function isQqApiHttpError(e: unknown): e is Error {
  return e instanceof Error && /^QQ API request failed .*: HTTP \d+/.test(e.message)
}

Try / catch

try {
  await qq.sendToChat(chatId, text)
} catch (e) {
  if (isQqApiHttpError(e)) {
    const status = Number(/HTTP (\d+)/.exec(e.message)?.[1] ?? 0)
    if (status === 401) { await qq.invalidateTokenAndRefresh(); return qq.sendToChat(chatId, text) }
    if (status === 429 || status >= 500) return backoffAndRetry()
  }
  throw e
}

Prevention

When it happens

Trigger: apiRequest() (line ~188) fetches an endpoint with Authorization: QQBot <token> and X-Union-Appid; response.ok is false; errorText is read and the message is thrown. Hit during getGatewayUrl, message POSTs, etc.

Common situations: Access token expired (QQ returns 401 — token cache TTL missed), insufficient bot permissions for the target user/group/channel, rate limited, or hitting an endpoint with the wrong X-Union-Appid.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/2ee7f2f72052ac91. Report an issue: GitHub.

Appendix: source

Thrown at src/main/ai/channels/adapters/qq/QqAdapter.ts:205

  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: {
        Authorization: `QQBot ${token}`,
        'Content-Type': 'application/json',
        'X-Union-Appid': this.appId
      },
      ...(options?.body ? { body: JSON.stringify(options.body) } : {})
    })

    if (!response.ok) {
      const errorText = await response.text().catch(() => '')
      throw new Error(`QQ API request failed ${endpoint}: HTTP ${response.status} - ${errorText}`)
    }

    return response
  }

  private async getGatewayUrl(): Promise<string> {
    const response = await this.apiRequest(`${QQ_API_BASE}/gateway`)
    const data = (await response.json()) as { url: string }
    return data.url
  }

  private async startGateway(): Promise<void> {
    if (this.isConnecting || this.shouldStop) return
    this.isConnecting = true

    try {
      this.cleanup()

View on GitHub (pinned to 726446b54c)