Skip to content

Commit 05d0d82

Browse files
HCLclawsweeper-repair
authored andcommitted
fix(feishu): skip empty-text messages with no media to prevent blank session turns (#74634)
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.
1 parent c4f9cf1 commit 05d0d82

3 files changed

Lines changed: 52 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai
4646
- 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.
4747
- 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.
4848
- 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.
49+
- 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.
4950
- 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.
5051
- 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.
5152
- 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.

extensions/feishu/src/bot.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2999,4 +2999,42 @@ describe("handleFeishuMessage command authorization", () => {
29992999
await Promise.all([dispatchMessage({ cfg, event }), dispatchMessage({ cfg, event })]);
30003000
expect(mockDispatchReplyFromConfig).toHaveBeenCalledTimes(1);
30013001
});
3002+
3003+
it("skips empty-text messages with no media to prevent blank user turns in session (#74634)", async () => {
3004+
// Feishu can deliver { "text": "" } events (empty-text or media-stripped
3005+
// messages). Writing blank user content to the session causes downstream
3006+
// LLM providers such as MiniMax to reject requests with "messages must not
3007+
// be empty". The handler should drop such events before queuing a reply.
3008+
mockShouldComputeCommandAuthorized.mockReturnValue(false);
3009+
3010+
const cfg: ClawdbotConfig = {
3011+
channels: {
3012+
feishu: {
3013+
dmPolicy: "open",
3014+
allowFrom: ["*"],
3015+
},
3016+
},
3017+
} as ClawdbotConfig;
3018+
3019+
const event: FeishuMessageEvent = {
3020+
sender: {
3021+
sender_id: {
3022+
open_id: "ou-empty-text-sender",
3023+
},
3024+
},
3025+
message: {
3026+
message_id: "msg-empty-text-74634",
3027+
chat_id: "oc-dm",
3028+
chat_type: "p2p",
3029+
message_type: "text",
3030+
// Feishu encodes empty text as {"text":""}
3031+
content: JSON.stringify({ text: "" }),
3032+
},
3033+
};
3034+
3035+
await dispatchMessage({ cfg, event });
3036+
3037+
// No reply should be dispatched: empty message is silently skipped
3038+
expect(mockDispatchReplyFromConfig).not.toHaveBeenCalled();
3039+
});
30023040
});

extensions/feishu/src/bot.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,19 @@ export async function handleFeishuMessage(params: {
859859
log,
860860
accountId: account.accountId,
861861
});
862+
// Skip messages with no text content and no media attachments. Feishu can
863+
// deliver empty-text events (e.g. `{"text":""}`) when a user sends a blank
864+
// message or when media parsing produces an empty string. Writing a blank
865+
// user turn to the session causes downstream LLM providers (e.g. MiniMax)
866+
// to reject the request with "messages must not be empty" errors. Logging
867+
// the skip avoids silent loss without polluting the agent session.
868+
if (!ctx.content.trim() && mediaList.length === 0) {
869+
log(
870+
`feishu[${account.accountId}]: skipping empty message (no text, no media) from ${ctx.senderOpenId}`,
871+
);
872+
return;
873+
}
874+
862875
const mediaPayload = buildAgentMediaPayload(mediaList);
863876
const audioTranscript = await resolveFeishuAudioPreflightTranscript({
864877
cfg: effectiveCfg,

0 commit comments

Comments
 (0)