CherryHQ/cherry-studio · error · Error
Client is not connected
Error message
Client is not connected
What it means
Thrown inside sendRawMessage() when this.client is falsy at send time. FeishuAdapter lazily holds the Lark Client; if it was never created (never connected) or was cleared on disconnect, any text send aborts here before the IM API call. sendMessage() calls sendRawMessage() after transitioning the reaction.
Source
Thrown at src/main/ai/channels/adapters/feishu/FeishuAdapter.ts:533
}
this.client = null
this.sendQrToRenderer('', 'disconnected')
this.log.info('Feishu bot stopped')
}
async sendMessage(chatId: string, text: string, _opts?: SendMessageOptions): Promise<void> {
void _opts
// Promote the typing reaction to DONE before delivering the reply,
// so the user sees the lifecycle transition. No-op for messages that
// weren't preceded by a typing indicator (e.g. /new acks).
await this.transitionChatReaction(chatId, REACTION_DONE, [REACTION_THINKING])
await this.sendRawMessage(chatId, text)
}
/** Send chunked text via the IM API without touching status reactions. */
private async sendRawMessage(chatId: string, text: string): Promise<void> {
if (!this.client) {
throw new Error('Client is not connected')
}
const chunks = splitMessage(text, FEISHU_MAX_LENGTH)
for (let i = 0; i < chunks.length; i++) {
ensureFeishuSuccess(
await this.client.im.message.create({
params: { receive_id_type: 'chat_id' },
data: {
receive_id: chatId,
msg_type: 'post',
content: buildPostPayload(chunks[i])
}
}),
'Send Feishu message'
)
if (i < chunks.length - 1) {View on GitHub (pinned to 726446b54c)
Solutions
- Check adapter isConnected / lifecycle state before enqueueing outbound messages; drop or queue them instead of calling sendMessage.
- Ensure performConnect fully resolves (client assigned and markConnected() called) before the message pump is allowed to run.
- On disconnect, drain or reject queued sends explicitly rather than letting them hit a null client.
Example fix
// before
private async sendRawMessage(chatId: string, text: string): Promise<void> {
if (!this.client) throw new Error('Client is not connected')
// after — fail with lifecycle context the caller can branch on
private async sendRawMessage(chatId: string, text: string): Promise<void> {
if (!this.client || !this.isConnected()) {
throw new Error(`Cannot send to ${chatId}: Feishu client not connected (state=${this.getState()})`)
} Defensive patterns
Strategy: validation
Validate before calling
// Only call sendMessage when the adapter reports connected.
if (!feishu.isConnected()) {
queueOrDrop(outbound)
} else {
await feishu.sendMessage(chatId, text)
} Type guard
function isFeishuNotConnected(e: unknown): e is Error {
return e instanceof Error && e.message === 'Client is not connected'
} Try / catch
try {
await feishu.sendMessage(chatId, text)
} catch (e) {
if (isFeishuNotConnected(e)) { enqueueForLater(chatId, text); return }
throw e
} Prevention
- Gate the outbound pump on isConnected and on connect having fully resolved.
- Drain or reject queued sends explicitly on disconnect.
- Treat 'Client is not connected' as a lifecycle state, not a transient retry.
When it happens
Trigger: sendMessage/sendRawMessage invoked while the adapter is disconnected, during the gap between connect start and client creation, after performDisconnect ran, or after a connection error that left client unset.
Common situations: Outbound queue draining after the bot was stopped, a /new ack path firing before initial connect completes, or a race where disconnect wins over an in-flight message.
Related errors
- Feishu WebSocket connection failed: ${error instanceof Error
- Registration polling aborted
- Bot is not connected
- Bot is not connected
- MCP runtime is stopping
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/7208194551aaaf28.
Report an issue: GitHub.