Skip to content

fix(email): enable splits_long_messages to avoid 4000-char truncation cap - #62000

Open
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:liuhao/cron-bugfix-61990
Open

fix(email): enable splits_long_messages to avoid 4000-char truncation cap#62000
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:liuhao/cron-bugfix-61990

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds splits_long_messages = True to EmailAdapter class attributes.

The delivery router (gateway/delivery.py) checks adapter.splits_long_messages at line 431 to decide whether to truncate content at MAX_PLATFORM_OUTPUT (4000 chars) or deliver the full payload. When True, the full content passes to the adapter's send() method, which can chunk natively. When False (the default), content above the cap is truncated with a footer pointing to a saved file.

EmailAdapter was missing this declaration, causing all email delivery to hit the truncation path even though:

  • SMTP has no practical message length limit at 4000 chars
  • The adapter's send() method handles the full payload via MIMEText(body, "plain", "utf-8")
  • The adapter internally defines MAX_MESSAGE_LENGTH = 50_000, indicating intent to support much longer messages

All other platform adapters (Telegram, Discord, Slack, Matrix, WhatsApp, etc.) already declare splits_long_messages = True. This change aligns email with their behavior and fixes the silent truncation bug where cron job output was cut off mid-content.

Related Issue

Fixes #61990

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • plugins/platforms/email/adapter.py: Added splits_long_messages = True class attribute with explanatory comment
  • tests/gateway/test_email.py: Added TestEmailAdapterSplitsLongMessages test class to verify the attribute is declared

How to Test

  1. Verify the test passes: pytest tests/gateway/test_email.py::TestEmailAdapterSplitsLongMessages -v
  2. Check that the adapter declares the attribute: python -c "from plugins.platforms.email.adapter import EmailAdapter; print(EmailAdapter.splits_long_messages)" should print True
  3. (Integration test) Configure email gateway adapter, create a cron job that generates output longer than 4000 characters (e.g., a daily briefing), and verify the received email contains the full output without truncation

Observed result: The test passes, confirming that EmailAdapter.splits_long_messages is True.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 26.5.1 (Apple Silicon)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/email Email (IMAP/SMTP) adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 10, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating the missing capability declaration. The current-head premise is valid: gateway/delivery.py:431 truncates when the flag is false, while EmailAdapter.send() forwards its body to MIMEText and smtp.send_message at plugins/platforms/email/adapter.py:893-954.

Problems

  • tests/gateway/test_email.py:1778 asserts only the implementation attribute. It does not exercise the reported delivery contract. The existing full-payload router coverage is generic (tests/gateway/test_delivery.py:334-351), not email-specific.
  • The base contract says splits_long_messages means send() splits through truncate_message() (gateway/platforms/base.py:2290-2296), but email sends one full SMTP body (plugins/platforms/email/adapter.py:949-954). The new comment should not call that chunking.

Suggested changes

  • Add a router-level >4000-character email regression test that captures EmailAdapter.send() input and asserts it equals the original content.
  • Describe email as preserving a native long message, or broaden the capability documentation accordingly.

Automated hermes-sweeper review.

# (SMTP handles arbitrary lengths, adapter internally defines MAX_MESSAGE_LENGTH=50_000).
# Enable native chunking so full payload reaches send() instead of being truncated
# at gateway/delivery.py's MAX_PLATFORM_OUTPUT (4000 chars) cap.
splits_long_messages = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

splits_long_messages is documented as send() splitting through truncate_message() in gateway/platforms/base.py:2290-2296, but this adapter sends one MIMEText body via SMTP. Please describe this as native full-message delivery (and consider broadening the base capability contract) rather than native chunking.

class TestEmailAdapterSplitsLongMessages(unittest.TestCase):
"""Verify EmailAdapter declares splits_long_messages=True to avoid truncation."""

def test_splits_long_messages_enabled(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a router-level regression using this adapter (with send captured) and a payload over MAX_PLATFORM_OUTPUT; asserting the class attribute alone does not prove that the email delivery route receives the full body.

…tract

Implements teknium1's feedback on PR NousResearch#62000:
- Clarify comment: email preserves full payload without chunking (not "native chunking")
- Add router-level regression test verifying >4000-char content reaches send() intact
- Test covers the actual delivery contract through DeliveryRouter, not just class attribute
@liuhao1024
liuhao1024 force-pushed the liuhao/cron-bugfix-61990 branch from a42e476 to f79efbc Compare July 11, 2026 11:23
@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 11, 2026
troyhoffman-oss added a commit to troyhoffman-oss/hermes-agent that referenced this pull request Aug 6, 2026
The email adapter never declared `splits_long_messages`, so the delivery
router (gateway/delivery.py) treats it as a non-chunking platform and
applies MAX_PLATFORM_OUTPUT = 4000 — a cap sized for Telegram's 4096-char
API limit. Email has no such constraint: the adapter already defines
MAX_MESSAGE_LENGTH = 50_000 (Gmail-safe body size) but never uses it.

Observed: a 5,199-char scheduled-job memo was cut to the 4,000-char
envelope, severing an escalation bullet, a deadline statement, and a
whole closing section before it reached the mailbox. There is no config
knob to opt out (issue NousResearch#61990).

send() now splits at MAX_MESSAGE_LENGTH via the shared truncate_message()
helper — the same pattern every peer adapter uses (Slack, Discord,
Telegram, Mattermost, Matrix, Teams, Feishu) — and the class declares
splits_long_messages = True so the router hands over the full payload.

Note on scope vs. the existing open PRs for this bug (NousResearch#62000, NousResearch#68900):
declaring splits_long_messages alone is not sufficient. That flag is a
promise to the router that the adapter chunks; upstream send() does no
chunking, so the flag by itself converts a 4,000-char truncation into an
unbounded body handed to SMTP. This change implements the chunking the
flag advertises, which is why MAX_MESSAGE_LENGTH stops being dead code.

No shared code changed: the router's cap stays correct for platforms that
genuinely have one.

Tests: 4 regression cases in tests/gateway/test_email.py, including an
end-to-end pass through the real DeliveryRouter asserting an oversized
payload arrives byte-identical with the audit copy still written.

Tested on: Linux (Ubuntu 24.04, Python 3.11).

Fixes NousResearch#61990
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/email Email (IMAP/SMTP) adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: No way to override email delivery truncation

3 participants