fix(cron): prevent delivery loop crash on multi-target email delivery - #47167
fix(cron): prevent delivery loop crash on multi-target email delivery#47167swissly wants to merge 3 commits into
Conversation
The standalone delivery path in _deliver_result() had an unhandled exception boundary: when asyncio.run() raised RuntimeError (running loop), the thread pool fallback could itself raise — but that exception escaped the except chain, crashing the entire delivery loop. Any targets after the first failure were silently skipped. Fix: wrap the thread pool fallback in its own try/except so failures are logged and the loop continues to remaining targets. Also fix the standalone _send_email() in send_message_tool.py to use SMTP_SSL for port 465 (implicit TLS), matching the fix already applied to the gateway email adapter in NousResearch#46084. Fixes NousResearch#47163
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Improves message delivery robustness by handling SMTP implicit TLS on port 465 and adding error handling around async delivery retries in the scheduler.
Changes:
- Use
SMTP_SSLfor implicit TLS on port 465; otherwise useSMTP+STARTTLS. - Wrap threaded
asyncio.run(_send_to_platform(...))retry in atry/exceptto record delivery failures and continue processing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| tools/send_message_tool.py | Add implicit TLS handling for SMTP port 465 and set socket timeouts. |
| cron/scheduler.py | Add exception handling around the threaded async retry path and append delivery errors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Port 465 requires implicit TLS (SMTP_SSL); other ports use STARTTLS. | ||
| if smtp_port == 465: | ||
| server = smtplib.SMTP_SSL(smtp_host, smtp_port, timeout=30) | ||
| else: | ||
| server = smtplib.SMTP(smtp_host, smtp_port, timeout=30) | ||
| server.starttls(context=ssl.create_default_context()) |
| server.starttls(context=ssl.create_default_context()) | ||
| # Port 465 requires implicit TLS (SMTP_SSL); other ports use STARTTLS. | ||
| if smtp_port == 465: | ||
| server = smtplib.SMTP_SSL(smtp_host, smtp_port, timeout=30) |
| with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: | ||
| future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, cleaned_delivery_content, thread_id=thread_id, media_files=media_files)) | ||
| result = future.result(timeout=30) | ||
| except Exception as e: |
| except Exception as e: | ||
| msg = f"delivery to {platform_name}:{chat_id} failed: {e}" | ||
| logger.error("Job '%s': %s", job["id"], msg) |
- Normalize smtp_port to int before comparison (string env vars) - Pass explicit SSL context to SMTP_SSL for consistent cert verification - Use explicit ThreadPoolExecutor shutdown(wait=False) for effective timeout - Add exc_info=True to error logs for full traceback preservation
|
Thanks for the thorough review! All 4 findings addressed in
|
Review update: overlap with #27680During self-review (Step 0 — duplicate search), I discovered that PR #27680 by @timothykersten already implements the SMTP_SSL fix for Action plan:
The two fixes are orthogonal concerns:
|
Address all remaining pr-preparation-checklist findings: - send_message_tool.py: wrap server.login/send in try/finally for SMTP cleanup - scheduler.py: add exc_info=True to outer exception handler (line 846) - tests: add TestMultiTargetDeliveryContinuesOnFailure (2 tests) All 138 scheduler tests pass.
|
Merged via #56269 — your fix landed on Notes on the salvage:
|
Summary
Fixes two bugs that prevent cron jobs from delivering to multiple email targets:
Delivery loop crash — The standalone path in
_deliver_result()had an unhandled exception boundary. Whenasyncio.run()raisedRuntimeError(running event loop), the thread pool fallback could itself raise — but that exception escaped theexceptchain, crashing the entire delivery loop. All targets after the first failure were silently skipped.Standalone SMTP_SSL — The standalone
_send_email()insend_message_tool.pyusedsmtplib.SMTP()+starttls()for all ports, which fails on port 465 (implicit TLS). This was already fixed in the gateway email adapter (fix(email): use implicit TLS for port 465 #46084) but not in the standalone send path.Fixes #47163
Changes
cron/scheduler.py: Wrap the thread pool fallback in_deliver_result()in its own try/except so failures are logged and the loop continues to remaining targets.tools/send_message_tool.py: UseSMTP_SSLfor port 465 in the standalone_send_email(), matching the gateway adapter's behavior.How to Verify
deliver: "email:user1@example.com,email:user2@example.com"Testing
py_compileon both files)Checklist
cron/scheduler.pyandtools/send_message_tool.pytouched (2 files, 15 insertions, 5 deletions)