{"record":{"id":"d350eb5176de895f","repo":"CherryHQ/cherry-studio","slug":"qq-bot-appid-and-clientsecret-are-required","errorCode":null,"errorMessage":"QQ Bot AppID and ClientSecret are required","messagePattern":"QQ Bot AppID and ClientSecret are required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/channels/adapters/qq/QqAdapter.ts","lineNumber":140,"sourceCode":"  private readonly DEDUP_MAX_ENTRIES = 500\n\n  constructor(config: ChannelAdapterConfig<'qq'>) {\n    super(config)\n    const { app_id, client_secret, allowed_chat_ids, mention_only } = config.channelConfig\n    this.appId = app_id\n    this.clientSecret = client_secret\n    this.allowedChatIds = allowed_chat_ids ?? []\n    this.notifyChatIds = [...this.allowedChatIds]\n    this.mentionOnly = mention_only ?? true\n  }\n\n  protected override async checkReady(): Promise<boolean> {\n    return !!(this.appId && this.clientSecret)\n  }\n\n  protected override async performConnect(_signal: AbortSignal): Promise<void> {\n    if (!this.appId || !this.clientSecret) {\n      throw new Error('QQ Bot AppID and ClientSecret are required')\n    }\n\n    this.shouldStop = false\n    await this.startGateway()\n\n    this.log.info('QQ bot started')\n  }\n\n  protected override async performDisconnect(): Promise<void> {\n    this.shouldStop = true\n    this.cleanup()\n    this.log.info('QQ bot stopped')\n  }\n\n  private async getAccessToken(): Promise<string> {\n    // Check cache\n    if (this.tokenCache && Date.now() < this.tokenCache.expiresAt - 60000) {\n      return this.tokenCache.accessToken","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/channels/adapters/qq/QqAdapter.ts#L122-L158","documentation":"Thrown by QqAdapter.performConnect() when appId or clientSecret is empty. It is the connect-time guard mirroring checkReady(); performConnect refuses to start the gateway if credentials are missing.","triggerScenarios":"Channel config loaded with empty/undefined app_id or client_secret; performConnect runs before credentials are supplied and throws before startGateway().","commonSituations":"User created the QQ channel but didn't fill in the AppID/ClientSecret from q.qq.com, env/config load order delivered empty strings, or a paste with stray whitespace.","solutions":["Obtain AppID and ClientSecret from the QQ Bot console (https://q.qq.com) and fill them in the channel config.","Trim whitespace when reading config; validate non-empty before allowing connect.","Rely on checkReady() to keep the adapter 'not ready' (grey) until credentials are present, preventing performConnect from firing."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Validate before allowing connect.\nfunction qqCredentialsPresent(cfg: QqChannelConfig): boolean {\n  return Boolean(cfg.app_id?.trim() && cfg.client_secret?.trim())\n}","typeGuard":"function isQqMissingCredentials(e: unknown): e is Error {\n  return e instanceof Error && e.message === 'QQ Bot AppID and ClientSecret are required'\n}","tryCatchPattern":"if (!qqCredentialsPresent(cfg)) { showConfigError('Enter QQ AppID and ClientSecret'); return }\ntry { await qq.connect() } catch (e) {\n  if (isQqMissingCredentials(e)) { showConfigError('Enter QQ AppID and ClientSecret'); return }\n  throw e\n}","preventionTips":["Obtain AppID/ClientSecret from https://q.qq.com and fill both fields.","Trim whitespace when reading config; keep the adapter 'not ready' (checkReady) until both are present.","Block the connect action in the UI until credentials are filled."],"tags":["qq","credentials","config","validation","lifecycle"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}