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.
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
- Bot is not connected
- Bot is not connected
- Feishu WebSocket connection failed
- MCP runtime is stopping
- Registration polling aborted
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/7208194551aaaf28.
Report an issue: GitHub.
Appendix: 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)