fix(feishu): skip empty-text messages with no media to prevent blank session turns (#74634) (#74661)

Feishu delivers empty-text events (e.g. {"text":""}) when users send
blank messages or when a media-only message produces no text content.
Writing a blank user turn to the session file causes downstream LLM
providers such as MiniMax to reject requests with:

  invalid params, messages must not be empty (2013)

Guard at the point after media resolution: if ctx.content.trim() is
empty AND mediaList is empty, log the skip and return without queuing
a reply. This preserves all existing behaviour for text, media, and
mixed messages.

Regression test: dispatch a DM with {"text":""} (no media), assert
mockDispatchReplyFromConfig is not called.

Closes #74634. Thanks @xdengli.
This commit is contained in:
hcl
2026-04-30 12:24:27 +08:00
committed by GitHub
parent 5716428adc
commit 38aac70830
3 changed files with 52 additions and 0 deletions

View File

@@ -2999,4 +2999,42 @@ describe("handleFeishuMessage command authorization", () => {
await Promise.all([dispatchMessage({ cfg, event }), dispatchMessage({ cfg, event })]);
expect(mockDispatchReplyFromConfig).toHaveBeenCalledTimes(1);
});
it("skips empty-text messages with no media to prevent blank user turns in session (#74634)", async () => {
// Feishu can deliver { "text": "" } events (empty-text or media-stripped
// messages). Writing blank user content to the session causes downstream
// LLM providers such as MiniMax to reject requests with "messages must not
// be empty". The handler should drop such events before queuing a reply.
mockShouldComputeCommandAuthorized.mockReturnValue(false);
const cfg: ClawdbotConfig = {
channels: {
feishu: {
dmPolicy: "open",
allowFrom: ["*"],
},
},
} as ClawdbotConfig;
const event: FeishuMessageEvent = {
sender: {
sender_id: {
open_id: "ou-empty-text-sender",
},
},
message: {
message_id: "msg-empty-text-74634",
chat_id: "oc-dm",
chat_type: "p2p",
message_type: "text",
// Feishu encodes empty text as {"text":""}
content: JSON.stringify({ text: "" }),
},
};
await dispatchMessage({ cfg, event });
// No reply should be dispatched: empty message is silently skipped
expect(mockDispatchReplyFromConfig).not.toHaveBeenCalled();
});
});

View File

@@ -859,6 +859,19 @@ export async function handleFeishuMessage(params: {
log,
accountId: account.accountId,
});
// Skip messages with no text content and no media attachments. Feishu can
// deliver empty-text events (e.g. `{"text":""}`) when a user sends a blank
// message or when media parsing produces an empty string. Writing a blank
// user turn to the session causes downstream LLM providers (e.g. MiniMax)
// to reject the request with "messages must not be empty" errors. Logging
// the skip avoids silent loss without polluting the agent session.
if (!ctx.content.trim() && mediaList.length === 0) {
log(
`feishu[${account.accountId}]: skipping empty message (no text, no media) from ${ctx.senderOpenId}`,
);
return;
}
const mediaPayload = buildAgentMediaPayload(mediaList);
const audioTranscript = await resolveFeishuAudioPreflightTranscript({
cfg: effectiveCfg,