CherryHQ/cherry-studio · error · Error

Failed to get access token: HTTP ${response.status}

Error message

Failed to get access token: HTTP ${response.status}

What it means

Thrown when the QQ AppAccessToken endpoint (https://bots.qq.com/app/getAppAccessToken) returns a non-2xx status. The message embeds response.status only; the body is not read. This token is mandatory for subsequent QQ API calls (Authorization: QQBot <token>).

Source

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

  }

  private async getAccessToken(): Promise<string> {
    // Check cache
    if (this.tokenCache && Date.now() < this.tokenCache.expiresAt - 60000) {
      return this.tokenCache.accessToken
    }

    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,

View on GitHub (pinned to 726446b54c)

Solutions

  1. Confirm appId/clientSecret are correct and match a QQ bot in the q.qq.com console.
  2. For 5xx or rate-limit status, back off and retry the token request.
  3. Read the response body for QQ's error detail to disambiguate 401 (bad creds) vs 429 (rate limit).
  4. Ensure the host can reach bots.qq.com without TLS interception.

Example fix

// before
if (!response.ok) {
  throw new Error(`Failed to get access token: HTTP ${response.status}`)
}

// after — include body for diagnosis and retry on transient 5xx
if (!response.ok) {
  const detail = await response.text().catch(() => '')
  if (response.status >= 500) {
    await new Promise((r) => setTimeout(r, 1000))
    return this.fetchAccessToken() // bounded retry
  }
  throw new Error(`Failed to get access token: HTTP ${response.status} - ${detail}`)
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: token endpoint reachable and credentials present.
function qqCanFetchToken(appId?: string, clientSecret?: string): boolean {
  return Boolean(appId?.trim() && clientSecret?.trim())
}

Type guard

function isQqTokenHttpError(e: unknown): e is Error {
  return e instanceof Error && /^Failed to get access token: HTTP \d+$/.test(e.message)
}

Try / catch

try {
  await qq.connect()
} catch (e) {
  if (isQqTokenHttpError(e)) {
    const status = Number(/HTTP (\d+)/.exec(e.message)?.[1] ?? 0)
    if (status >= 500) return retryConnect()
    notifyUser('QQ credentials rejected or token endpoint down')
    return
  }
  throw e
}

Prevention

When it happens

Trigger: net.fetch POST to getAppAccessToken with {appId, clientSecret} returns response.ok === false; the guard throws HTTP <status> before parsing JSON.

Common situations: Wrong appId/clientSecret (QQ returns 4xx), QQ service degradation (5xx), network/proxy returning an error page, or rate limiting on the token endpoint.

Related errors


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