fix(email): split long cron output instead of truncating at 4,000 chars - #79842
Closed
troyhoffman-oss wants to merge 1 commit into
Closed
fix(email): split long cron output instead of truncating at 4,000 chars#79842troyhoffman-oss wants to merge 1 commit into
troyhoffman-oss wants to merge 1 commit into
Conversation
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
troyhoffman-oss
force-pushed
the
fix/email-split-long-cron-output
branch
from
August 6, 2026 02:40
21bcd72 to
d680fa3
Compare
Author
|
Withdrawing — submitted in error against operator instruction. The analysis stands if a maintainer wants it: the 3 duplicate PRs set splits_long_messages=True without implementing chunking in send(), converting truncation into an unbounded SMTP body; this branch implements the chunking the flag advertises. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
EmailAdapternever declaressplits_long_messages, sogateway/delivery.pytreats email as a non-chunking platform and appliesMAX_PLATFORM_OUTPUT = 4000(delivery.py:29, 488, 503). That cap is sized for Telegram's 4096-char API limit. Email has no such constraint — the adapter already definesMAX_MESSAGE_LENGTH = 50_000(Gmail-safe body size, adapter.py:110) but never uses it.Result: any scheduled-job output over 4,000 chars is silently cut before it reaches the mailbox, with no way to opt out (#61990).
Observed data loss. A 5,199-char cron memo was truncated to the 4,000-char envelope, severing an escalation bullet, a deadline statement, and a whole closing section. Nothing in the delivered mail indicated content was missing.
Why this isn't a duplicate of #62000 / #68900
Those PRs set
splits_long_messages = Trueand stop there. That flag is a promise to the router that the adapter chunks — but upstreamsend()does no chunking at all:Setting the flag alone converts a 4,000-char truncation into an unbounded body handed to SMTP, past the 50,000-char limit the adapter itself declares. This PR implements the chunking the flag advertises — which is precisely why
MAX_MESSAGE_LENGTHstops being dead code.How it works
send()splits atMAX_MESSAGE_LENGTHvia the sharedtruncate_message()helper — the same pattern every peer adapter uses (Slack, Discord, Telegram, Mattermost, Matrix, Teams, Feishu) — and the class declaressplits_long_messages = Trueso the router hands over the full payload.No shared code changed. The router's cap stays correct for platforms that genuinely have one; behavior for every other adapter is untouched.
How to test
4 new regression cases in
tests/gateway/test_email.py:test_declares_native_chunkingtest_send_delivers_long_content_wholetest_send_splits_above_body_limittest_router_does_not_truncate_email_deliveryDeliveryRouter: oversized payload arrives byte-identical, audit copy still writtenManual reproduction: configure an email platform, schedule a cron job whose output exceeds 4,000 chars, compare the delivered body against
~/.hermes/cron/output/<job_id>_*.txt(the audit copy is written whole — it is the delivered mail that loses the tail).Platforms tested
Linux (Ubuntu 24.04, Python 3.11.15). No OS-specific code paths touched — the change is adapter-level string handling plus one class attribute.
Fixes #61990