Bug type
Behavior bug (incorrect output/state without crash)
Summary
Feishu plugin's per-chat serial queue (createChatQueue in extensions/feishu/src/monitor.account.ts) blocks queued messages from reaching the gateway's followup queue, making messages.queue.mode = "collect" ineffective — each message is processed as a separate agent turn instead of being batched.
Steps to reproduce
- Set messages.queue.mode = "collect" and messages.queue.debounceMs = 2000 in openclaw.json.
- Send a message to the Feishu bot that triggers a long agent run (e.g. ask it to run
sleep 30).
- While the agent is processing, send 3 additional messages (spacing does not matter).
- Observe: after message 1 completes, messages 2, 3, and 4 are processed individually as separate agent turns, not batched.
Expected behavior
Messages 2, 3, and 4 should be batched into a single followup turn via the collect queue, as documented for messages.queue.mode = "collect". This is the behavior observed on Discord and other channels that do not implement a per-chat serial queue.
Actual behavior
Each message is processed as an independent agent turn in sequence. Gateway logs show no "enqueue-followup" action for messages 2-4 — they all enter resolveActiveRunQueueAction with isActive=false and receive "run-now".
Root cause: createChatQueue() in extensions/feishu/src/monitor.account.ts wraps each message in a task chained via prev.then(task, task). Each task awaits the full dispatchReplyFromConfig → agent run cycle. When task A completes, isActive becomes false before task B starts, so the gateway treats B as a new request rather than a followup.
OpenClaw version
2026.3.23
Operating system
Ubuntu 24.04 (WSL2)
Install method
npm global
Model
google-vertex/gemini-3.1-pro-preview
Provider / routing chain
openclaw -> google-vertex (direct)
Additional provider/model setup details
NOT_ENOUGH_INFO
Logs, screenshots, and evidence
# Relevant source: extensions/feishu/src/monitor.account.ts
# The per-chat serial queue:
function createChatQueue() {
const queues = new Map<string, Promise<void>>();
return (chatId: string, task: () => Promise<void>): Promise<void> => {
const prev = queues.get(chatId) ?? Promise.resolve();
const next = prev.then(task, task); // <-- blocks until previous task completes
queues.set(chatId, next);
void next.finally(() => {
if (queues.get(chatId) === next) queues.delete(chatId);
});
return next;
};
}
# The dispatch function wraps each message as a serial task:
const dispatchFeishuMessage = async (event: FeishuMessageEvent) => {
const chatId = event.message.chat_id?.trim() || "unknown";
const task = () => handleFeishuMessage({ cfg, event, ... });
await enqueue(chatId, task); // <-- awaits full agent run before next message can start
};
# Gateway's decision function (pi-embedded-DgYXShcG.js):
function resolveActiveRunQueueAction(params) {
if (!params.isActive) return "run-now"; // <-- always hit because serial queue waits
if (params.isHeartbeat) return "drop";
if (params.shouldFollowup || params.queueMode === "steer") return "enqueue-followup";
return "run-now";
}
# Timeline showing why collect never activates:
# msg A: task starts -> isActive=true -> agent runs -> task ends -> isActive=false
# msg B: [blocked in Promise chain] ──────────────────────────> task starts (isActive=false!) -> "run-now"
# msg C: [blocked in Promise chain] ──────────────────────────────────────────────────────> same pattern
# The official @larksuiteoapi/feishu-openclaw-plugin@2026.3.8 has the same architecture
# (chat-queue.js with enqueueFeishuChatTask using identical Promise chaining).
Impact and severity
Affected: All Feishu channel users who configure messages.queue.mode = "collect"
Severity: Medium (feature silently ineffective, no crash or data loss)
Frequency: Always (100% reproducible)
Consequence: Users who send multiple messages while agent is busy get separate replies for each, consuming extra API tokens and creating fragmented conversation context. The documented collect feature appears broken for Feishu.
Additional information
Workaround: bypass the serial queue when a task is already active for the same chat, so the
message reaches the gateway while isActive is still true:
function createChatQueue() {
const queues = new Map<string, Promise>();
const enqueue = (chatId: string, task: () => Promise): Promise => {
const prev = queues.get(chatId) ?? Promise.resolve();
const next = prev.then(task, task);
queues.set(chatId, next);
void next.finally(() => { if (queues.get(chatId) === next) queues.delete(chatId); });
return next;
};
enqueue.has = (chatId: string): boolean => queues.has(chatId);
return enqueue;
}
// In dispatchFeishuMessage:
if (enqueue.has(chatId)) {
void task().catch((err) => error(bypassed dispatch failed: ${err}));
return;
}
await enqueue(chatId, task);
This preserves ordering for the first message while letting subsequent messages reach the
gateway's followup queue. Verified working on 2026.3.2.
A potentially cleaner upstream fix might be at the gateway level — e.g. having
dispatchReplyFromConfig detect an active session and enqueue the followup internally,
rather than requiring each channel plugin to bypass its own serial queue.
Note: the Feishu plugin correctly sets OriginatingTo (bot.ts line 1301), so
resolveCrossChannelKey is not a factor in this issue.
Bug type
Behavior bug (incorrect output/state without crash)
Summary
Feishu plugin's per-chat serial queue (createChatQueue in extensions/feishu/src/monitor.account.ts) blocks queued messages from reaching the gateway's followup queue, making messages.queue.mode = "collect" ineffective — each message is processed as a separate agent turn instead of being batched.
Steps to reproduce
sleep 30).Expected behavior
Messages 2, 3, and 4 should be batched into a single followup turn via the collect queue, as documented for messages.queue.mode = "collect". This is the behavior observed on Discord and other channels that do not implement a per-chat serial queue.
Actual behavior
Each message is processed as an independent agent turn in sequence. Gateway logs show no "enqueue-followup" action for messages 2-4 — they all enter resolveActiveRunQueueAction with isActive=false and receive "run-now".
Root cause: createChatQueue() in extensions/feishu/src/monitor.account.ts wraps each message in a task chained via prev.then(task, task). Each task awaits the full dispatchReplyFromConfig → agent run cycle. When task A completes, isActive becomes false before task B starts, so the gateway treats B as a new request rather than a followup.
OpenClaw version
2026.3.23
Operating system
Ubuntu 24.04 (WSL2)
Install method
npm global
Model
google-vertex/gemini-3.1-pro-preview
Provider / routing chain
openclaw -> google-vertex (direct)
Additional provider/model setup details
NOT_ENOUGH_INFO
Logs, screenshots, and evidence
Impact and severity
Affected: All Feishu channel users who configure messages.queue.mode = "collect"
Severity: Medium (feature silently ineffective, no crash or data loss)
Frequency: Always (100% reproducible)
Consequence: Users who send multiple messages while agent is busy get separate replies for each, consuming extra API tokens and creating fragmented conversation context. The documented collect feature appears broken for Feishu.
Additional information
Workaround: bypass the serial queue when a task is already active for the same chat, so the
message reaches the gateway while isActive is still true:
function createChatQueue() {
const queues = new Map<string, Promise>();
const enqueue = (chatId: string, task: () => Promise): Promise => {
const prev = queues.get(chatId) ?? Promise.resolve();
const next = prev.then(task, task);
queues.set(chatId, next);
void next.finally(() => { if (queues.get(chatId) === next) queues.delete(chatId); });
return next;
};
enqueue.has = (chatId: string): boolean => queues.has(chatId);
return enqueue;
}
// In dispatchFeishuMessage:
if (enqueue.has(chatId)) {
void task().catch((err) => error(
bypassed dispatch failed: ${err}));return;
}
await enqueue(chatId, task);
This preserves ordering for the first message while letting subsequent messages reach the
gateway's followup queue. Verified working on 2026.3.2.
A potentially cleaner upstream fix might be at the gateway level — e.g. having
dispatchReplyFromConfig detect an active session and enqueue the followup internally,
rather than requiring each channel plugin to bypass its own serial queue.
Note: the Feishu plugin correctly sets OriginatingTo (bot.ts line 1301), so
resolveCrossChannelKey is not a factor in this issue.