Summary
message tool cannot send a plain file to a Feishu chat using action: "send" when the tool schema includes poll-related fields. Any send call that happens to carry poll params (which the model tends to auto-fill from the schema) is rejected with:
Poll fields require action "poll"; use action "poll" instead of "send".
This makes ordinary "send + attachment" semantics unusable for Feishu when the general message tool schema exposes poll creation parameters.
Environment
- Runtime: OpenClaw (macOS, /opt/homebrew install)
- Path in use:
/opt/homebrew/lib/node_modules/openclaw/dist
- Channel: Feishu
- Tool:
message (agent tools.message)
- Action:
send
- Payload: plain text message + local txt file attachment
Expected behavior
action: "send" should allow sending a normal message (optionally with an attachment) on Feishu, even if the generic message tool supports polls on other channels.
- The presence of poll-related fields in the tool schema should not break
send semantics for channels that don't need polls, especially when the user is just trying to send a file.
Actual behavior
When using the message tool with action: "send" on Feishu to send a local txt file, the call fails with:
Poll fields require action "poll"; use action "poll" instead of "send".
From the calling side:
- Semantic intent is:
action = "send", channel = Feishu, message + txt attachment.
- The tool schema, however, includes a full set of poll fields (
pollQuestion, pollOption, pollDurationHours, pollMulti, pollDurationSeconds, pollAnonymous, pollPublic, etc.).
- The model tends to populate these poll fields based on the schema, even when the prompt says "don't send a poll".
- As a result, the generic guard sees
hasPollCreationParams(params) == true on a send action and throws the error above, before the request ever reaches Feishu.
Relevant code paths (reverse-engineered from dist)
From the compiled bundle in /opt/homebrew/lib/node_modules/openclaw/dist (filenames shortened for clarity):
- Message tool schema construction
function buildMessageToolSchemaProps(options) {
return {
...buildRoutingSchema(),
...buildSendSchema(options),
...buildReactionSchema(),
...buildFetchSchema(),
...buildPollSchema({ includeTelegramExtras: options.includeTelegramPollExtras }),
...buildChannelTargetSchema(),
...buildStickerSchema(),
...buildThreadSchema(),
...buildEventSchema(),
...buildModerationSchema(),
...buildGatewaySchema(),
...buildChannelManagementSchema(),
...buildPresenceSchema(),
};
}
buildPollSchema expands both poll creation fields and poll voting fields into the same argument object that action: "send" uses.
- Poll param detection guard
function hasPollCreationParams(params) {
for (const key of POLL_CREATION_PARAM_NAMES) {
const def = POLL_CREATION_PARAM_DEFS[key];
const value = readPollParamRaw(params, key);
// string / stringArray / number / boolean checks ...
// any non-empty / true / finite value → true
}
return false;
}
// later in the dispatch path
if (action === "send" && hasPollCreationParams(params)) {
throw new Error(
'Poll fields require action "poll"; use action "poll" instead of "send".'
);
}
Behaviorally, this means:
- If any poll creation field has a "truthy" value (non-empty string, finite number,
true, etc.), hasPollCreationParams returns true.
- Combined with
action === "send", the guard throws the error above.
- Since the tool schema exposes these poll fields unconditionally, the model tends to fill them even when the user only wants to send a file, making the error effectively unavoidable in real agent flows.
Why this is a problem in practice
In an agent-driven environment:
- Models generally try to fill all visible/encouraged fields from a tool schema, especially optional ones that look relevant.
- Asking the model via prompt "don't use poll fields" is a soft constraint and not reliable.
- As long as the tool schema exposes poll creation parameters on the same
message tool used for plain send, agent calls to Feishu send + attachment will randomly trip the guard, even though Feishu itself doesn't need (or support) those poll parameters for simple file sends.
In this specific case, that made it impossible to:
- Use the
message tool to send a simple txt file to a Feishu chat with action: "send".
Local production hotfix (for context only)
On this machine, to unblock Feishu file sends immediately, I applied a dist-level hotfix under:
/opt/homebrew/lib/node_modules/openclaw/dist
Important: this is not a proposed upstream fix, just documenting what was done locally so you understand the symptom and the minimal remediation that worked in practice.
Changes (applied across several dist bundles: reply-*, plugin-sdk/reply-*, plugin-sdk/dispatch-*, pi-embedded-*, compact-*):
- Remove poll schema from message tool props:
- ...buildFetchSchema(),
- ...buildPollSchema({ includeTelegramExtras: options.includeTelegramPollExtras }),
- ...buildChannelTargetSchema(),
+ ...buildFetchSchema(),
+ ...buildChannelTargetSchema(),
- Remove the send+poll guard:
-if (action === "send" && hasPollCreationParams(params)) {
- throw new Error(
- 'Poll fields require action "poll"; use action "poll" instead of "send".'
- );
-}
After this change and a openclaw gateway restart, a Feishu message tool call with:
action: "send"
- channel: Feishu
- message + local txt file
started working as expected (file delivered, no poll error).
Suggested upstream fix
Instead of relying on dist hotfixes, a more robust upstream fix could be one of:
-
Separate schemas for send vs poll
- Don't expose poll creation fields on the same schema used for generic
send.
- Keep
poll as a distinct action with its own (narrow) schema, so models can't accidentally apply poll fields when the user only wants send + attachment.
-
Action-aware sanitization
- Keep the unified schema if necessary, but at runtime:
- For
action: "send", automatically strip all poll creation fields from params before the guard / channel handler.
- Optionally, keep a soft log/diagnostic if poll params were present (to help detect model misuse), but don't throw.
- Reserve the strict
"Poll fields require action \"poll\"" error for cases where the caller actually uses action: "poll" with malformed poll input.
-
Channel-aware poll support
- For channels where polls are not supported (or not implemented yet), don't include poll fields in the message tool schema at all.
- That would also reduce schema size and make agent behavior more predictable per-channel.
Request
Please adjust the message tool design so that:
action: "send" for Feishu (and other channels) is not blocked just because poll creation fields exist in the shared schema.
- Either by splitting schemas, sanitizing poll fields when
action=send, or by scoping poll fields only to channels/actions that explicitly support them.
Happy to share local bundle snippets or a minimal reproducer yaml if that helps.
Summary
messagetool cannot send a plain file to a Feishu chat usingaction: "send"when the tool schema includes poll-related fields. Anysendcall that happens to carry poll params (which the model tends to auto-fill from the schema) is rejected with:This makes ordinary "send + attachment" semantics unusable for Feishu when the general message tool schema exposes poll creation parameters.
Environment
/opt/homebrew/lib/node_modules/openclaw/distmessage(agent tools.message)sendExpected behavior
action: "send"should allow sending a normal message (optionally with an attachment) on Feishu, even if the generic message tool supports polls on other channels.sendsemantics for channels that don't need polls, especially when the user is just trying to send a file.Actual behavior
When using the
messagetool withaction: "send"on Feishu to send a local txt file, the call fails with:From the calling side:
action = "send", channel = Feishu, message + txt attachment.pollQuestion,pollOption,pollDurationHours,pollMulti,pollDurationSeconds,pollAnonymous,pollPublic, etc.).hasPollCreationParams(params) == trueon asendaction and throws the error above, before the request ever reaches Feishu.Relevant code paths (reverse-engineered from dist)
From the compiled bundle in
/opt/homebrew/lib/node_modules/openclaw/dist(filenames shortened for clarity):buildPollSchemaexpands both poll creation fields and poll voting fields into the same argument object thataction: "send"uses.Behaviorally, this means:
true, etc.),hasPollCreationParamsreturns true.action === "send", the guard throws the error above.Why this is a problem in practice
In an agent-driven environment:
messagetool used for plainsend, agent calls to Feishusend + attachmentwill randomly trip the guard, even though Feishu itself doesn't need (or support) those poll parameters for simple file sends.In this specific case, that made it impossible to:
messagetool to send a simple txt file to a Feishu chat withaction: "send".Local production hotfix (for context only)
On this machine, to unblock Feishu file sends immediately, I applied a dist-level hotfix under:
/opt/homebrew/lib/node_modules/openclaw/distImportant: this is not a proposed upstream fix, just documenting what was done locally so you understand the symptom and the minimal remediation that worked in practice.
Changes (applied across several dist bundles:
reply-*,plugin-sdk/reply-*,plugin-sdk/dispatch-*,pi-embedded-*,compact-*):After this change and a
openclaw gateway restart, a Feishumessagetool call with:action: "send"started working as expected (file delivered, no poll error).
Suggested upstream fix
Instead of relying on dist hotfixes, a more robust upstream fix could be one of:
Separate schemas for
sendvspollsend.pollas a distinct action with its own (narrow) schema, so models can't accidentally apply poll fields when the user only wantssend + attachment.Action-aware sanitization
action: "send", automatically strip all poll creation fields fromparamsbefore the guard / channel handler."Poll fields require action \"poll\""error for cases where the caller actually usesaction: "poll"with malformed poll input.Channel-aware poll support
Request
Please adjust the message tool design so that:
action: "send"for Feishu (and other channels) is not blocked just because poll creation fields exist in the shared schema.action=send, or by scoping poll fields only to channels/actions that explicitly support them.Happy to share local bundle snippets or a minimal reproducer yaml if that helps.