hermes - ✅(Solved) Fix WeCom outbound markdown silently truncates long Hermes responses at 4000 characters [3 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
NousResearch/hermes-agent#19689Fetched 2026-05-05 06:05:32
View on GitHub
Comments
0
Participants
1
Timeline
8
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×4labeled ×4

Error Message

  • return an error/warning when the full content cannot be delivered.

Root Cause

The WeCom adapter enforces the platform message length limit by slicing the string inline, but it does not preserve overflow content, split into chunks, or annotate the truncation. Because the API call can still succeed, the delivery layer reports success while the user receives only a prefix of the answer.

Fix Action

Fixed

PR fix notes

PR #19751: fix(wecom): split long outbound markdown instead of silent truncation

Description (problem / solution / changelog)

Summary

Fixes #19689 — WeCom outbound markdown responses were silently truncated at 4000 characters. Everything beyond MAX_MESSAGE_LENGTH was dropped with no indication to the user.

Changes

  • gateway/platforms/wecom.py: Replace content[:self.MAX_MESSAGE_LENGTH] with self.truncate_message() (inherited from BasePlatformAdapter) which splits long content into multiple chunks preserving code block boundaries and adding chunk indicators (e.g. (1/2)).
    • In send(): iterate over chunks — first chunk uses passive reply path if available, remaining chunks use proactive APP_CMD_SEND.
    • In _send_reply_markdown(): remove inline truncation (caller now pre-chunks).
  • tests/gateway/test_wecom.py: Add TestSendLongMessageSplitting with 3 tests:
    • Long message is split into multiple API calls, each within limit
    • Short message is sent as single call (no regression)
    • Reply path: first chunk uses reply, overflow uses proactive send

Testing

All 44 existing WeCom tests pass + 3 new tests pass.

Root Cause

Both _send_reply_markdown() and the proactive path in send() used content[:self.MAX_MESSAGE_LENGTH] to enforce the WeCom 4000-char limit. Since the WeCom API accepts truncated payloads without error, Hermes reported success while the user lost all content beyond 4000 chars.

Changed files

  • gateway/platforms/wecom.py (modified, +24/-15)
  • tests/gateway/test_wecom.py (modified, +60/-0)

PR #19811: fix(wecom): split long markdown into multiple messages instead of silently truncating

Description (problem / solution / changelog)

Summary

  • WeCom outbound markdown is silently truncated at 4000 characters: both the proactive send path and the passive _send_reply_markdown path slice with content[:self.MAX_MESSAGE_LENGTH] before sending. The WeCom API accepts the request, so Hermes reports success while everything past 4000 chars is dropped from the user's view.
  • Switch send() to chunk via BasePlatformAdapter.truncate_message (already used by Discord, Telegram, Feishu, Matrix). The first chunk rides the inbound reply context when present; subsequent chunks fall back to proactive APP_CMD_SEND since the WeCom reply_req_id is single-use.

The bug

gateway/platforms/wecom.py:1214 and gateway/platforms/wecom.py:1359 (pre-fix line numbers):

"markdown": {"content": content[:self.MAX_MESSAGE_LENGTH]},

A 6000-character agent response sends one 4000-char message; the remaining 2000 chars are silently discarded. SendResult still reports success=True. The user has no signal that content was lost.

Reported in #19689.

The fix

In WeComAdapter.send:

chunks = self.truncate_message(content, self.MAX_MESSAGE_LENGTH)

response: Dict[str, Any] = {}
for index, chunk in enumerate(chunks):
    # WeCom reply_req_id is single-use: only the first chunk can ride
    # the inbound reply context. Subsequent chunks fall back to the
    # proactive APP_CMD_SEND path, which is stateless.
    if index == 0 and reply_req_id:
        response = await self._send_reply_markdown(reply_req_id, chunk)
    else:
        response = await self._send_request(
            APP_CMD_SEND,
            {
                "chatid": chat_id,
                "msgtype": "markdown",
                "markdown": {"content": chunk},
            },
        )
    error = self._response_error(response)
    if error:
        return SendResult(success=False, error=error)

truncate_message already preserves code-block fences across chunk boundaries and adds (1/N) indicators within MAX_MESSAGE_LENGTH. The redundant content[:self.MAX_MESSAGE_LENGTH] slice in _send_reply_markdown is dropped as well: the only caller (send) pre-chunks now, and the slice read like an ongoing truncation contract that no longer applies.

If a chunk fails (e.g. WeCom errcode), send() aborts and returns the failure rather than continuing to fire follow-ups against an already-rejecting endpoint.

Test plan

  • Focused regression: tests/gateway/test_wecom.py::TestSend — 4 new tests cover (a) long proactive markdown splits into multiple APP_CMD_SEND calls, each ≤ MAX_MESSAGE_LENGTH; (b) long content with reply context uses passive reply for chunk 1 and proactive APP_CMD_SEND for chunks 2+; (c) short messages still produce exactly one send with content unchanged; (d) early-chunk failure aborts the send loop.
  • Adjacent suite: tests/gateway/test_wecom.py (45 passed), tests/gateway/test_wecom_callback.py + tests/gateway/test_text_batching.py (31 passed).
  • Regression guard: with the production change reverted, test_send_splits_long_proactive_markdown_into_multiple_messages and test_send_long_message_first_chunk_uses_reply_then_proactive both fail (await_count == 1 instead of >= 2) — confirming the silent-truncation behavior the fix removes.

Related

Fixes #19689.

Sibling code paths that may need the same fix: gateway/platforms/dingtalk.py (lines around the two content[: self.MAX_MESSAGE_LENGTH] slices, with limit 20000) and gateway/platforms/homeassistant.py:405 (limit 4096) both still slice instead of chunking. Intentionally left out of this PR's scope to keep the diff small and focused on the reported WeCom bug — happy to widen if preferred.

Changed files

  • gateway/platforms/wecom.py (modified, +22/-16)
  • tests/gateway/test_wecom.py (modified, +93/-0)

PR #20006: fix(wecom): split long markdown sends

Description (problem / solution / changelog)

Summary

  • split long WeCom markdown replies instead of slicing to 4000 characters
  • split proactive WeCom markdown sends with the same shared chunking behavior
  • keep single-chunk responses backward-compatible while exposing chunk raw responses for split sends

Fixes #19689

Tests

  • scripts/run_tests.sh tests/gateway/test_wecom.py
  • git diff --check

Changed files

  • gateway/platforms/wecom.py (modified, +41/-17)
  • tests/gateway/test_wecom.py (modified, +48/-1)
RAW_BUFFERClick to expand / collapse

Bug Description

WeCom outbound markdown responses are silently truncated at 4000 characters instead of being split into multiple messages or marked as truncated.

Both reply markdown and proactive send paths slice content with content[:self.MAX_MESSAGE_LENGTH] before sending. If WeCom accepts the request, Hermes reports success, but everything after the first 4000 characters is lost from the user's perspective.

Inspected against main commit af6f9bc2a12682b06fb3632acf5a9cbf01e74a85.

Steps to Reproduce

Static code path:

  1. Generate or send a WeCom response longer than 4000 characters.
  2. The WeCom adapter sends markdown via either the reply path or proactive path.
  3. The adapter slices the content to content[:self.MAX_MESSAGE_LENGTH] before sending.
  4. Hermes returns success if the WeCom API accepts the truncated payload.

Expected Behavior

Long WeCom text/markdown responses should not lose content silently. Hermes should either:

  • split long responses into multiple WeCom messages;
  • add a clear truncation marker and/or attach the full content; or
  • return an error/warning when the full content cannot be delivered.

Actual Behavior

Content after 4000 characters is dropped silently.

Evidence

  • gateway/platforms/wecom.py:89
    • defines MAX_MESSAGE_LENGTH = 4000.
  • gateway/platforms/wecom.py:1209-1215
    • _send_reply_markdown() sends "markdown": {"content": content[:self.MAX_MESSAGE_LENGTH]}.
  • gateway/platforms/wecom.py:1332-1360
    • proactive send() uses the same content[:self.MAX_MESSAGE_LENGTH] slicing.

Affected Component

Gateway / WeCom

Environment

Static analysis of current main; no local environment-specific behavior required.

Prior Art Checked

I searched existing issues and PRs for:

  • WeCom long message split truncate
  • content[:self.MAX_MESSAGE_LENGTH] wecom
  • WeCom 4000 markdown

The results were unrelated translation/meta issues or unrelated PRs; I did not find an exact existing issue or PR for silent WeCom response truncation.

Root Cause Analysis

The WeCom adapter enforces the platform message length limit by slicing the string inline, but it does not preserve overflow content, split into chunks, or annotate the truncation. Because the API call can still succeed, the delivery layer reports success while the user receives only a prefix of the answer.

Proposed Fix

Use the shared message-splitting behavior used by other platform adapters, or add an adapter-local splitter that sends sequential markdown chunks up to the WeCom limit. If splitting is not viable for a specific WeCom send mode, add an explicit truncation marker and return enough metadata for callers/logs to know the response was shortened.

extent analysis

TL;DR

Implement message splitting or truncation handling for WeCom responses to prevent silent truncation of content exceeding 4000 characters.

Guidance

  • Review the gateway/platforms/wecom.py file to understand the current implementation of MAX_MESSAGE_LENGTH and its usage in _send_reply_markdown() and send() methods.
  • Consider adopting the shared message-splitting behavior used by other platform adapters to handle long WeCom responses.
  • If splitting is not feasible, add an explicit truncation marker to indicate that the response has been shortened and provide metadata for logging purposes.
  • Verify that the proposed fix handles both reply and proactive send paths correctly.

Example

# Example of message splitting (simplified for illustration)
def split_message(content, max_length):
    chunks = []
    while content:
        chunk = content[:max_length]
        chunks.append(chunk)
        content = content[max_length:]
    return chunks

# Usage in _send_reply_markdown() or send()
chunks = split_message(content, self.MAX_MESSAGE_LENGTH)
for chunk in chunks:
    # Send each chunk as a separate message
    self.send_markdown(chunk)

Notes

The proposed fix should be tested thoroughly to ensure it works correctly for both reply and proactive send paths, and that it handles edge cases such as empty or very long messages.

Recommendation

Apply a workaround by implementing message splitting or truncation handling, as upgrading to a fixed version is not mentioned in the issue. This approach allows for a targeted solution to the specific problem of silent truncation in WeCom responses.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix WeCom outbound markdown silently truncates long Hermes responses at 4000 characters [3 pull requests, 1 participants]