CherryHQ/cherry-studio · error · Error

Bot is not connected

Error message

Bot is not connected

What it means

Thrown by WeChatAdapter.sendMessage() when this.bot (a WeixinBot instance) is null. The bot is set in performConnect() (WeChatAdapter.ts:46) and nulled in performDisconnect() (line 82). WeChat login is a multi-step QR flow that can take many seconds, so the window where sendMessage is called before connect completes is larger than for the other adapters. After disconnect, this.bot is null and sends fail with this message.

Source

Thrown at src/main/ai/channels/adapters/wechat/WeChatAdapter.ts:91

      }
    })

    this.log.info('WeChat bot started')
  }

  protected override async performDisconnect(): Promise<void> {
    if (this.bot) {
      this.bot.stop()
      this.bot = null
      this.sendQrToRenderer('', 'disconnected')
      this.log.info('WeChat bot stopped')
    }
  }

  // oxlint-disable-next-line no-unused-vars -- abstract method signature
  async sendMessage(chatId: string, text: string, _opts?: SendMessageOptions): Promise<void> {
    if (!this.bot) {
      throw new Error('Bot is not connected')
    }

    const chunks = splitMessage(text, WECHAT_MAX_LENGTH)

    for (let i = 0; i < chunks.length; i++) {
      await this.bot.send(chatId, chunks[i])

      if (i < chunks.length - 1) {
        await new Promise((resolve) => setTimeout(resolve, 100))
      }
    }
  }

  override async sendFile(chatId: string, file: FileAttachment): Promise<void> {
    if (!this.bot) {
      throw new Error('Bot is not connected')
    }
    // The reverse-engineered WeChat protocol only supports outbound images today

View on GitHub (pinned to 726446b54c)

Solutions

  1. Gate outbound sends on the adapter connected state, and specifically on WeChat login completion (markConnected is called at WeChatAdapter.ts:64 only after credentials are obtained).
  2. Queue messages while the channel is in QR-pending or reconnecting state and flush on markConnected.
  3. Have performDisconnect await/drain pending sends before nulling this.bot.
  4. Subscribe callers to channel-state events so they stop sending on disconnect.

Example fix

// before — races with the long WeChat QR login
await wechatAdapter.sendMessage(chatId, text)

// after — check the connected flag set only after login
if (!wechatAdapter.isConnected()) {
  await pendingWeChatSends.enqueue({ chatId, text })
  return
}
await wechatAdapter.sendMessage(chatId, text)
Defensive patterns

Strategy: validation

Validate before calling

// WeChat login is slow and interactive; check the connected state (set only after
// markConnected at WeChatAdapter.ts:64) and queue while in QR-pending or reconnecting.
if (!wechatAdapter.isConnected()) {
  await pendingWeChatSends.enqueue({ chatId, text })
  return
}
await wechatAdapter.sendMessage(chatId, text)

Type guard

// WeChat login completion is signaled by markConnected — use the base class flag
function isWeChatReadyToSend(adapter: ChannelAdapter): boolean {
  return adapter.isConnected()
}

Try / catch

try {
  await wechatAdapter.sendMessage(chatId, text)
} catch (e) {
  if (e instanceof Error && e.message === 'Bot is not connected') {
    // WeChat disconnects are often session-expiry re-logins; queue and retry
    await pendingWeChatSends.enqueue({ chatId, text })
    return
  }
  throw e
}

Prevention

When it happens

Trigger: sendMessage() called before performConnect() finished the QR login flow (bot is assigned at line 46 before login completes, but a connect failure leaves it null); or after performDisconnect() nulled it. Because WeChat requires an interactive QR scan, the adapter can sit in a 'pending QR' state for a long time during which this.bot may be set but not yet logged in — sending in that window fails downstream rather than here.

Common situations: A scheduled notification fires while the WeChat channel is waiting for QR scan (user has not scanned yet); the WeChat session expired and runLoop() is re-authenticating (login flow blocks the loop); the user disconnected the channel and a queued send drained after teardown.

Related errors


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