HKUDS/Vibe-Trading · warning · ValueError

Feishu message content must not be empty

Error message

Feishu message content must not be empty

What it means

Raised by FeishuChannel.send_with_receipt when sending a plain text message (no media, reply, or metadata) whose content strips to empty. It is a client-side validation before any HTTP call, because the Feishu im/v1/messages API rejects empty text payloads. Uses ValueError rather than RuntimeError since it's caller input, not provider state.

Source

Thrown at agent/src/channels/feishu.py:2209

        except Exception:
            self.logger.exception("Error sending message")
            raise

    async def send_with_receipt(self, msg: OutboundMessage) -> DeliveryReceipt:
        """Send a non-reply message and retain Feishu's provider message id.

        Scheduled briefings use this path. Rich reply/media sends keep the
        generic adapter contract because Feishu's reply and upload APIs do not
        expose one uniform receipt shape.
        """
        if msg.media or msg.reply_to or msg.metadata:
            return await super().send_with_receipt(msg)
        if not self._client:
            raise RuntimeError("Feishu client is not initialized")
        content = (msg.content or "").strip()
        if not content:
            raise ValueError("Feishu message content must not be empty")

        receive_id_type = "chat_id" if msg.chat_id.startswith("oc_") else "open_id"
        loop = asyncio.get_running_loop()
        payloads: list[tuple[str, str]] = []
        fmt = self._detect_msg_format(content)
        if fmt == "text":
            payloads.append(("text", json.dumps({"text": content}, ensure_ascii=False)))
        elif fmt == "post":
            payloads.append(("post", self._markdown_to_post(content)))
        else:
            elements = self._build_card_elements(content)
            for chunk in self._split_elements_by_table_limit(elements):
                payloads.append(
                    (
                        "interactive",
                        json.dumps(
                            {"config": {"wide_screen_mode": True}, "elements": chunk},
                            ensure_ascii=False,

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Check/trim msg.content before sending and skip or substitute a placeholder when empty
  2. Fix the upstream producer (prompt/LLM output) so it never yields empty content
  3. If you intended to send media or a reply, populate msg.media or msg.reply_to so the base channel path is used

Example fix

// before
await channel.send_with_receipt(OutboundMessage(chat_id=chat, content="   "))

// after
text = (content or "").strip()
if not text:
    text = "(empty response)"
await channel.send_with_receipt(OutboundMessage(chat_id=chat, content=text))
Defensive patterns

Strategy: validation

Validate before calling

content = (msg.content or "").strip()
if not content and not (msg.media or msg.reply_to):
    skip_or_placeholder = True  # don't call send_with_receipt as-is

Type guard

def has_sendable_text(msg: OutboundMessage) -> bool:
    return bool((msg.content or "").strip()) or bool(msg.media or msg.reply_to or msg.metadata)

Try / catch

null

Prevention

When it happens

Trigger: Calling send_with_receipt with an OutboundMessage whose content is '', None, or only whitespace, while msg.media, msg.reply_to, and msg.metadata are all falsy (those routes delegate to the base channel instead).

Common situations: Upstream LLM/agent produced an empty completion, templating rendered an empty string, whitespace-only content after trimming user input, or a bug where content was assigned to the wrong field.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/968fb3728ce233d7. Report an issue: GitHub.