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

  1. Read the embedded errorText to see QQ's actual 200-body shape (often an inline {code,message} error).
  2. If QQ returned an inline error, address the root cause (e.g. invalid clientSecret) — the HTTP status alone was misleading.
  3. If the schema changed, update the type cast and field reads to match the new response.
  4. 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

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

Related errors


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