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

@@ -48,6 +48,7 @@ Docs: https://docs.openclaw.ai
- Agents/output: drop copied inbound metadata-only assistant replay turns before provider replay instead of synthesizing a placeholder, so Telegram and other channels cannot receive `[assistant copied inbound metadata omitted]` as model output. Fixes #74745. Thanks @adamwdear and @Marvae.
- Doctor/memory: suppress skipped embedding-readiness warnings for key-optional providers such as Ollama and LM Studio while preserving timeout and not-ready diagnostics. Fixes #74608 and #73882. Thanks @hclsys.
- Channels/groups: preserve observe-only turn suppression for prepared dispatch paths and restore deprecated channel turn runtime aliases, so passive observer/group flows stay silent while older plugins keep compiling. Thanks @vincentkoc.
- Feishu: skip empty-text messages (e.g. `{"text":""}`) that carry no media, so no blank user turn is written to the session and downstream LLM providers cannot reject the request with "messages must not be empty". (#74634) Thanks @xdengli and @hclsys.
- Feishu/Bitable: clean up newly created placeholder rows whose fields contain only default empty values while preserving meaningful link, attachment, user, number, boolean, and location values during create-app cleanup. (#73920) Carries forward #40602. Thanks @boat2moon.
- macOS app: keep attach-only mode and the Debug Settings launchd toggle marker-only, so launching with `--attach-only`/`--no-launchd` no longer uninstalls the Gateway LaunchAgent or drops active sessions. (#72174) Thanks @DolencLuka.
- Plugin SDK: restore the deprecated `plugin-sdk/zalouser` command-auth facade so published Lark/Zalo plugins that import it load on current hosts. Fixes #74702. Thanks @Goron01.

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,