Skip to content

[Bug]: DingTalk adapter broken with dingtalk-stream >= 0.20 (async process/start) #9752

@chenweigao

Description

@chenweigao

Bug Description

The DingTalk adapter fails to receive messages with newer versions of dingtalk-stream SDK (>=
0.20).

Root Cause

dingtalk-stream changed two methods from sync to async:

  1. ChatbotHandler.process() — was def, now async def. Returns a tuple (STATUS_OK, "OK") which
    the SDK tries to await, causing "object tuple can't be used in 'await' expression".
  2. DingTalkStreamClient.start() — was sync (blocking), now async def. The adapter wraps it
    with asyncio.to_thread() which doesn't work for coroutines, causing "coroutine
    'DingTalkStreamClient.start' was never awaited".
  3. process() receives CallbackMessage, not ChatbotMessage — The message data (text,
    senderId, sessionWebhook, etc.) is inside message.data dict, not as direct attributes.
    _extract_text() gets nothing and logs "Empty message, skipping".

Steps to Reproduce

pip install "dingtalk-stream>=0.20"
hermes gateway run # with dingtalk configured

Send a message in DingTalk → no response

Error Logs

ERROR dingtalk_stream.client: error processing message: object tuple can't be used in 'await'
expression
RuntimeWarning: coroutine 'DingTalkStreamClient.start' was never awaited
ERROR dingtalk_stream.client: [start] network exception, error=

Suggested Fix

_IncomingHandler.process() — make async, parse CallbackMessage.data:

async def process(self, message):
data = message.data
if isinstance(data, str):
data = json.loads(data)
msg = ChatbotMessage()
msg.message_id = data.get('msgId', '')
msg.text = data.get('text', {})
msg.sender_id = data.get('senderId', '')
msg.sender_nick = data.get('senderNick', '')
# ... map all fields from data dict
await self._adapter._on_message(msg)
return dingtalk_stream.AckMessage.STATUS_OK, "OK"

_run_stream() — start() is async but needs its own event loop (it runs a blocking websocket
loop internally). Running it in the gateway's event loop causes [start] network exception,
error=. Needs to run in a separate thread with its own event loop, with a thread-safe queue to
pass messages back.

Environment

  • Hermes Agent v0.9.0
  • dingtalk-stream 0.24.3
  • Python 3.12

Steps to Reproduce

  1. pip install "dingtalk-stream>=0.20"
  2. Configure DingTalk in config.yaml:
    platforms:
    dingtalk:
    enabled: true
    Set DINGTALK_CLIENT_ID and DINGTALK_CLIENT_SECRET in .env
  3. hermes gateway run
  4. Send any message to the bot in DingTalk
  5. No response. Errors in log:
    • "coroutine 'DingTalkStreamClient.start' was never awaited"
    • "error processing message: object tuple can't be used in 'await' expression"
    • "[start] network exception, error="

Expected Behavior

dingtalk success

Actual Behavior

ERROR dingtalk_stream.client: error processing message: object tuple can't be used in 'await'
expression
RuntimeWarning: coroutine 'DingTalkStreamClient.start' was never awaited
ERROR dingtalk_stream.client: [start] network exception, error=

Affected Component

Agent Core (conversation loop, context compression, memory)

Messaging Platform (if gateway-related)

Telegram, N/A (CLI only)

Debug Report

ERROR dingtalk_stream.client: error processing message: object tuple can't be used in 'await'
  expression
  RuntimeWarning: coroutine 'DingTalkStreamClient.start' was never awaited
  ERROR dingtalk_stream.client: [start] network exception, error=

Operating System

centos 8

Python Version

3.12

Hermes Version

No response

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

_IncomingHandler.process() — make async, parse CallbackMessage.data:

async def process(self, message):
data = message.data
if isinstance(data, str):
data = json.loads(data)
msg = ChatbotMessage()
msg.message_id = data.get('msgId', '')
msg.text = data.get('text', {})
msg.sender_id = data.get('senderId', '')
msg.sender_nick = data.get('senderNick', '')
# ... map all fields from data dict
await self._adapter._on_message(msg)
return dingtalk_stream.AckMessage.STATUS_OK, "OK"

_run_stream() — start() is async but needs its own event loop (it runs a blocking websocket
loop internally). Running it in the gateway's event loop causes [start] network exception,
error=. Needs to run in a separate thread with its own event loop, with a thread-safe queue to
pass messages back.

Proposed Fix (optional)

No response

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions