openclaw - ✅(Solved) Fix [Bug]: Feishu message tool accepts empty card ({}) causing 400 API error - blocks proactive message sending [4 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#54430Fetched 2026-04-08 01:27:39
View on GitHub
Comments
0
Participants
1
Timeline
7
Reactions
0
Participants
Timeline (top)
cross-referenced ×4referenced ×2labeled ×1

Error Message

const card = ctx.params.card && typeof ctx.params.card === "object" ? ctx.params.card : void 0; // Problem: {} is truthy and typeof {} === "object" → card = {} const text = readFirstString(ctx.params, ["text", "message"]); // Validation passes because card is truthy (even though empty) if (!card && !text && !mediaUrl) throw new Error(...); // Sends empty card to Feishu API → 400 error if (card) result = await runtime.sendCardFeishu({ card, ... });

Steps to reproduce

  1. Set up a Cron task that sends periodic reports to Feishu
  2. When the Cron triggers, the AI attempts to send a message via the message tool
  3. If the AI passes card={} or an empty card object, OpenClaw accepts it
  4. Feishu API rejects the empty card with 400 error
  5. Message fails to send

Expected behavior

  • OpenClaw should validate that card payloads contain actual content before sending
  • OR OpenClaw should fall back to sendMessageFeishu (plain text) when card is empty
  • OR the validation should reject empty card objects at the tool parameter level

Actual behavior

When a Cron task triggers and the AI attempts to send a Feishu message via the message tool, the message fails with a 400 API error. The Feishu plugin returns: ⚠️ ✉️ Message failed Evidence from gateway log:

  • AI passes card={} (empty card object) to the message tool
  • OpenClaw core (channel-1opGQxxp.js:605) accepts {} as valid because typeof {} === "object" evaluates to truthy
  • Validation if (!card && !text && !mediaUrl) passes since card is truthy (even though empty)
  • OpenClaw calls runtime.sendCardFeishu({ card: {} }) which sends the empty card to Feishu API
  • Feishu API returns 400 error: "invalid card payload - card content cannot be empty"
  • Message delivery fails silently or shows "Message failed" in Feishu This blocks ALL proactive Feishu message sending from Cron tasks, including:
  • Server GPU monitoring reports
  • Daily server usage reports
  • Training task schedule notifications

OpenClaw version

2026.3.23-2

Operating system

Linux (containerized deployment)

Install method

No response

Model

minimax m2.7

Provider / routing chain

Feishu → OpenClaw Gateway → Feishu API

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Root Cause

Bug Summary

The Feishu message tool's action handler in OpenClaw core (channel-1opGQxxp.js) accepts an empty card object {} as valid input, which passes the internal validation check but then causes a 400 error from the Feishu API because empty cards are not valid.

OpenClaw Version

2026.3.23-2

Platform

Linux (containerized deployment)

Root Cause Location

/usr/lib/node_modules/openclaw/dist/channel-1opGQxxp.js lines 605-622

const card = ctx.params.card && typeof ctx.params.card === "object" ? ctx.params.card : void 0;
// Problem: {} is truthy and typeof {} === "object" → card = {}
const text = readFirstString(ctx.params, ["text", "message"]);
// Validation passes because card is truthy (even though empty)
if (!card && !text && !mediaUrl) throw new Error(...);
// Sends empty card to Feishu API → 400 error
if (card) result = await runtime.sendCardFeishu({ card, ... });

PR fix notes

PR #54511: fix: reject empty card objects in Feishu message tool (#54430)

Description (problem / solution / changelog)

Summary

  • Fixes #54430
  • Empty card objects {} pass the existing typeof card === "object" check but cause 400 errors from the Feishu API
  • Added validation to reject empty card objects before making the API call

Test plan

  • Send a Feishu message with card: {} and no text — should return a validation error
  • Send a Feishu message with a valid card — should work as before

Changed files

  • extensions/feishu/src/channel.test.ts (modified, +14/-0)
  • extensions/feishu/src/channel.ts (modified, +5/-2)

PR #54654: fix(feishu): validate card has content before sending - reject empty cards

Description (problem / solution / changelog)

Fixes #54430

Problem

Empty card objects ({}) passed validation but caused 400 API errors from Feishu, blocking proactive message sending in Cron tasks.

Root Cause

The send/edit actions checked typeof card === "object" which is truthy for empty objects, bypassing the validation that requires card, text, or media.

Solution

  • Add isValidFeishuCard() helper to check if card has content (at least one key)
  • Use helper in send/edit validation to reject empty cards early
  • Empty cards now fall back to text-only send path instead of API error

Testing

  • ✅ pnpm build
  • ✅ pnpm check
  • ✅ pnpm lint
  • ✅ pnpm test

All quality gates pass. Empty cards are now properly rejected before reaching the Feishu API.

Changed files

  • extensions/feishu/src/channel.ts (modified, +16/-5)

PR #54906: fix(feishu): ignore empty card payloads

Description (problem / solution / changelog)

Summary

  • treat card: {} as an absent card payload instead of forwarding it to the Feishu API
  • let plain text sends continue when a model includes an empty card object alongside valid text
  • add regression coverage for both the text fallback path and the empty-card validation path

Closes #54430.

Testing

  • CI=1 pnpm exec vitest run extensions/feishu/src/channel.test.ts
  • pnpm exec oxlint --type-aware extensions/feishu/src/channel.ts extensions/feishu/src/channel.test.ts

Changed files

  • extensions/feishu/src/channel.test.ts (modified, +38/-0)
  • extensions/feishu/src/channel.ts (modified, +10/-8)

PR #54934: Feishu: reject empty message cards

Description (problem / solution / changelog)

Summary

  • Problem: Feishu message actions treated card: {} as a valid card-only payload, then forwarded it to Feishu and triggered a 400 API error.
  • Why it matters: proactive Feishu sends can fail when the model emits an empty card object, especially in cron/reporting flows.
  • What changed: reject empty Feishu card payloads at the channel action/runtime send layers, and fall back to plain-text sends when message is present alongside an empty card.
  • What did NOT change (scope boundary): card-only sends remain supported for non-empty card payloads, and other channels/plugins were not changed.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #54430
  • Related #
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

  • Root cause: Feishu send/thread-reply handling only checked typeof card === "object" / Boolean(card), so an empty object passed validation as a card payload.
  • Missing detection / guardrail: tests covered non-empty card sends but did not lock in behavior for card: {} or direct sendCardFeishu({ card: {} }).
  • Prior context (git blame, prior PR, issue, or refactor if known): Unknown.
  • Why this regressed now: the message action path tolerated structurally empty card objects instead of validating that the payload contained card content.
  • If unknown, what was ruled out: ruled out Feishu API-side formatting issues by tracing the failure to local validation and dispatch before the API request.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/feishu/src/channel.test.ts, extensions/feishu/src/send.reply-fallback.test.ts, extensions/feishu/src/send.test.ts
  • Scenario the test should lock in: empty card-only sends are rejected, empty card + text falls back to text, and direct Feishu send/edit helpers also reject empty cards.
  • Why this is the smallest reliable guardrail: the regression is pure input validation/dispatch logic and does not require a live Feishu API call to verify.
  • Existing test that already covers this (if any): existing Feishu tests only covered non-empty card sends.
  • If no new test is added, why not:

User-visible / Behavior Changes

  • Feishu message tool calls with card: {} now fail fast with a clear local error instead of sending a bad request to Feishu.
  • When text is present alongside an empty card, OpenClaw sends the text message instead of attempting a broken card send.

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local Node 22 / pnpm workspace
  • Model/provider: N/A
  • Integration/channel (if any): Feishu
  • Relevant config (redacted): Feishu channel enabled

Steps

  1. Invoke the Feishu message tool with action=send, channel=feishu, and card={}.
  2. Observe the local validation / dispatch behavior.
  3. Repeat with both message text and card={}.

Expected

  • Empty card-only payloads should be rejected locally.
  • Empty card + text should still send the text payload.

Actual

  • Before this change, {} was treated as a valid card and produced a Feishu 400 error.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: ran pnpm test -- extensions/feishu/src/channel.test.ts extensions/feishu/src/send.reply-fallback.test.ts extensions/feishu/src/send.test.ts; all passed with the new empty-card cases.
  • Edge cases checked: empty card-only payload, empty card plus text fallback, direct sendCardFeishu, and editMessageFeishu.
  • What you did not verify: live Feishu API behavior beyond the mocked unit tests.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this commit.
  • Files/config to restore: extensions/feishu/src/channel.ts, extensions/feishu/src/send.ts
  • Known bad symptoms reviewers should watch for: legitimate non-empty Feishu cards being rejected, or text fallback not triggering when both message and empty card are present.

Risks and Mitigations

  • Risk: the new guard could reject malformed-but-previously-tolerated payloads that some custom caller relied on.
    • Mitigation: the change is limited to truly empty objects/arrays/null-ish cards; non-empty card-only sends are still covered by existing tests plus the new guardrail cases.

Changed files

  • extensions/feishu/src/channel.test.ts (modified, +38/-0)
  • extensions/feishu/src/channel.ts (modified, +7/-4)
  • extensions/feishu/src/send.reply-fallback.test.ts (modified, +13/-0)
  • extensions/feishu/src/send.test.ts (modified, +36/-0)
  • extensions/feishu/src/send.ts (modified, +17/-1)

Code Example

const card = ctx.params.card && typeof ctx.params.card === "object" ? ctx.params.card : void 0;
// Problem: {} is truthy and typeof {} === "object" → card = {}
const text = readFirstString(ctx.params, ["text", "message"]);
// Validation passes because card is truthy (even though empty)
if (!card && !text && !mediaUrl) throw new Error(...);
// Sends empty card to Feishu API → 400 error
if (card) result = await runtime.sendCardFeishu({ card, ... });


### Steps to reproduce

1. Set up a Cron task that sends periodic reports to Feishu
2. When the Cron triggers, the AI attempts to send a message via the message tool
3. If the AI passes card={} or an empty card object, OpenClaw accepts it
4. Feishu API rejects the empty card with 400 error
5. Message fails to send

### Expected behavior

- OpenClaw should validate that card payloads contain actual content before sending
- OR OpenClaw should fall back to sendMessageFeishu (plain text) when card is empty
- OR the validation should reject empty card objects at the tool parameter level

### Actual behavior

When a Cron task triggers and the AI attempts to send a Feishu message via the message tool, the message fails with a 400 API error. The Feishu plugin returns:
⚠️ ✉️ Message failed
Evidence from gateway log:
- AI passes `card={}` (empty card object) to the message tool
- OpenClaw core (`channel-1opGQxxp.js:605`) accepts `{}` as valid because `typeof {} === "object"` evaluates to truthy
- Validation `if (!card && !text && !mediaUrl)` passes since `card` is truthy (even though empty)
- OpenClaw calls `runtime.sendCardFeishu({ card: {} })` which sends the empty card to Feishu API
- Feishu API returns 400 error: "invalid card payload - card content cannot be empty"
- Message delivery fails silently or shows "Message failed" in Feishu
This blocks ALL proactive Feishu message sending from Cron tasks, including:
- Server GPU monitoring reports
- Daily server usage reports
- Training task schedule notifications

### OpenClaw version

2026.3.23-2

### Operating system

Linux (containerized deployment)

### Install method

_No response_

### Model

minimax m2.7

### Provider / routing chain

FeishuOpenClaw GatewayFeishu API

### Additional provider/model setup details

_No response_

### Logs, screenshots, and evidence
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Summary

Bug Summary

The Feishu message tool's action handler in OpenClaw core (channel-1opGQxxp.js) accepts an empty card object {} as valid input, which passes the internal validation check but then causes a 400 error from the Feishu API because empty cards are not valid.

OpenClaw Version

2026.3.23-2

Platform

Linux (containerized deployment)

Root Cause Location

/usr/lib/node_modules/openclaw/dist/channel-1opGQxxp.js lines 605-622

const card = ctx.params.card && typeof ctx.params.card === "object" ? ctx.params.card : void 0;
// Problem: {} is truthy and typeof {} === "object" → card = {}
const text = readFirstString(ctx.params, ["text", "message"]);
// Validation passes because card is truthy (even though empty)
if (!card && !text && !mediaUrl) throw new Error(...);
// Sends empty card to Feishu API → 400 error
if (card) result = await runtime.sendCardFeishu({ card, ... });


### Steps to reproduce

1. Set up a Cron task that sends periodic reports to Feishu
2. When the Cron triggers, the AI attempts to send a message via the message tool
3. If the AI passes card={} or an empty card object, OpenClaw accepts it
4. Feishu API rejects the empty card with 400 error
5. Message fails to send

### Expected behavior

- OpenClaw should validate that card payloads contain actual content before sending
- OR OpenClaw should fall back to sendMessageFeishu (plain text) when card is empty
- OR the validation should reject empty card objects at the tool parameter level

### Actual behavior

When a Cron task triggers and the AI attempts to send a Feishu message via the message tool, the message fails with a 400 API error. The Feishu plugin returns:
⚠️ ✉️ Message failed
Evidence from gateway log:
- AI passes `card={}` (empty card object) to the message tool
- OpenClaw core (`channel-1opGQxxp.js:605`) accepts `{}` as valid because `typeof {} === "object"` evaluates to truthy
- Validation `if (!card && !text && !mediaUrl)` passes since `card` is truthy (even though empty)
- OpenClaw calls `runtime.sendCardFeishu({ card: {} })` which sends the empty card to Feishu API
- Feishu API returns 400 error: "invalid card payload - card content cannot be empty"
- Message delivery fails silently or shows "Message failed" in Feishu
This blocks ALL proactive Feishu message sending from Cron tasks, including:
- Server GPU monitoring reports
- Daily server usage reports
- Training task schedule notifications

### OpenClaw version

2026.3.23-2

### Operating system

Linux (containerized deployment)

### Install method

_No response_

### Model

minimax m2.7

### Provider / routing chain

FeishuOpenClaw GatewayFeishu API

### Additional provider/model setup details

_No response_

### Logs, screenshots, and evidence

```shell

Impact and severity

  • Blocked: Cron tasks cannot proactively send Feishu messages (e.g., server monitoring reports)
  • Affected flow: AI agent → message tool (action=send, channel=feishu) → Cron scheduled reports
  • Error received: Feishu API returns 400 error for empty/invalid card payload

Additional information

No response

extent analysis

Fix Plan

To fix the issue, we need to modify the validation check in channel-1opGQxxp.js to reject empty card objects. We can do this by adding a check for the presence of keys in the card object.

Step-by-Step Solution:

  1. Update the validation check: Modify the line where card is assigned to also check if the object is not empty.
const card = ctx.params.card && typeof ctx.params.card === "object" && Object.keys(ctx.params.card).length > 0 ? ctx.params.card : void 0;
  1. Alternative: Fall back to plain text message: If the card object is empty, fall back to sending a plain text message using runtime.sendMessageFeishu.
if (card) {
  result = await runtime.sendCardFeishu({ card, ... });
} else if (text) {
  result = await runtime.sendMessageFeishu({ text, ... });
}
  1. Additional validation: Consider adding a more robust validation for the card object to ensure it conforms to the expected format.

Verification

To verify the fix, follow these steps:

  • Trigger the Cron task that sends periodic reports to Feishu.
  • Check the Feishu API response to ensure that it no longer returns a 400 error.
  • Verify that the message is delivered successfully, either as a card or a plain text message.

Extra Tips

  • Consider adding logging to track when an empty card object is detected and a plain text message is sent instead.
  • Review the documentation for the Feishu API to ensure that the card object is correctly formatted and contains the required fields.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

  • OpenClaw should validate that card payloads contain actual content before sending
  • OR OpenClaw should fall back to sendMessageFeishu (plain text) when card is empty
  • OR the validation should reject empty card objects at the tool parameter level

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING