Skip to content

fix(matrix): add formatted_body with HTML rendering for cron deliveries - #5236

Closed
ygd58 wants to merge 1 commit into
NousResearch:mainfrom
ygd58:fix/matrix-cron-markdown-formatting
Closed

fix(matrix): add formatted_body with HTML rendering for cron deliveries#5236
ygd58 wants to merge 1 commit into
NousResearch:mainfrom
ygd58:fix/matrix-cron-markdown-formatting

Conversation

@ygd58

@ygd58 ygd58 commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #5233

Cron deliveries to Matrix now include formatted_body with HTML-rendered markdown. Also converts h1-h6 to bold for Element X compatibility.

Gracefully falls back to plain text if markdown library is unavailable.

@fxfitz

fxfitz commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Nice fix! This addresses the exact issue from #5233 — cron deliveries to Matrix missing formatted_body. The core approach (markdown → HTML, Element X h-tag workaround, graceful fallback) is spot-on.

A few suggestions after reviewing the diff:

1. Regex should handle header attributes

The current pattern:

html = _re.sub(r'<h[1-6]>(.*?)</h[1-6]>', r'<p><strong>\1</strong></p>', html)

Won't match headers with attributes like <h2 id="summary">. The markdown library can produce these depending on extensions. A more robust pattern:

html = _re.sub(r'<h[1-6][^>]*>(.*?)</h[1-6]>', r'<p><strong>\1</strong></p>', html)

2. Catch all exceptions, not just ImportError

If markdown is installed but throws on unexpected input (malformed text, extension conflicts, etc.), the current code would propagate the exception and break the entire message delivery. Consider:

try:
    import markdown as _md
    import re as _re
    html = _md.markdown(message, extensions=["nl2br", "fenced_code", "tables"])
    html = _re.sub(r'<h[1-6][^>]*>(.*?)</h[1-6]>', r'<p><strong>\1</strong></p>', html)
    if html != message:
        msg_content["format"] = "org.matrix.custom.html"
        msg_content["formatted_body"] = html
except ImportError:
    pass  # markdown not installed
except Exception as err:
    logger.warning("_send_matrix: markdown conversion failed: %s", err)

This way it always falls back to plain text rather than risking a send failure.

3. Consider adding logging

A pass on the ImportError makes debugging harder in production. Even a logger.debug or logger.warning would help operators understand why their messages aren't rendering as HTML.

4. Minor: single-paragraph unwrapping

For short one-line messages, markdown wraps the output in <p>...</p>. This is technically fine per the Matrix spec, but stripping the wrapper when there's only a single paragraph produces cleaner HTML:

if html.count("<p>") == 1:
    html = html.replace("<p>", "").replace("</p>", "")

Not a blocker — just a polish thing.


Overall this is a clean, minimal fix that solves the problem. The suggestions above are mostly about resilience and edge cases. Thanks for picking this up so quickly! 🙏


This review was generated by a Hermes agent running claude-opus-4-6 (Anthropic).

teknium1 added a commit that referenced this pull request Apr 5, 2026
…origin fallback

Salvaged from PRs #3767 (chalkers), #5236 (ygd58), #2641 (buntingszn).

Three improvements to Matrix cron delivery:

1. Live adapter path: when the gateway is running, cron delivery now uses
   the connected MatrixAdapter via run_coroutine_threadsafe instead of
   the standalone HTTP PUT. This enables delivery to E2EE rooms where
   the raw HTTP path cannot encrypt. Falls back to standalone on failure.
   Threads adapters + event loop from gateway -> cron ticker -> tick() ->
   _deliver_result(). (from #3767)

2. HTML formatted_body: _send_matrix() now converts markdown to HTML
   using the optional markdown library, with h1-h6 to bold conversion
   for Element X compatibility. Falls back to plain text if markdown
   is not installed. Also adds random bytes to txn_id to prevent
   collisions. (from #5236)

3. Origin fallback: when deliver="origin" but origin is null (jobs
   created via API/scripts), falls back to HOME_CHANNEL env vars
   in order: matrix -> telegram -> discord -> slack. (from #2641)
@teknium1

teknium1 commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Your formatted_body HTML rendering was incorporated into PR #5271 — _send_matrix() now converts markdown to HTML with Element X compatibility. Your authorship is credited. Thanks!

@teknium1 teknium1 closed this Apr 5, 2026
teknium1 added a commit that referenced this pull request Apr 5, 2026
…origin fallback

Salvaged from PRs #3767 (chalkers), #5236 (ygd58), #2641 (buntingszn).

Three improvements to Matrix cron delivery:

1. Live adapter path: when the gateway is running, cron delivery now uses
   the connected MatrixAdapter via run_coroutine_threadsafe instead of
   the standalone HTTP PUT. This enables delivery to E2EE rooms where
   the raw HTTP path cannot encrypt. Falls back to standalone on failure.
   Threads adapters + event loop from gateway -> cron ticker -> tick() ->
   _deliver_result(). (from #3767)

2. HTML formatted_body: _send_matrix() now converts markdown to HTML
   using the optional markdown library, with h1-h6 to bold conversion
   for Element X compatibility. Falls back to plain text if markdown
   is not installed. Also adds random bytes to txn_id to prevent
   collisions. (from #5236)

3. Origin fallback: when deliver="origin" but origin is null (jobs
   created via API/scripts), falls back to HOME_CHANNEL env vars
   in order: matrix -> telegram -> discord -> slack. (from #2641)
Tommyeds pushed a commit to Tommyeds/hermes-agent that referenced this pull request Apr 12, 2026
…origin fallback

Salvaged from PRs NousResearch#3767 (chalkers), NousResearch#5236 (ygd58), NousResearch#2641 (buntingszn).

Three improvements to Matrix cron delivery:

1. Live adapter path: when the gateway is running, cron delivery now uses
   the connected MatrixAdapter via run_coroutine_threadsafe instead of
   the standalone HTTP PUT. This enables delivery to E2EE rooms where
   the raw HTTP path cannot encrypt. Falls back to standalone on failure.
   Threads adapters + event loop from gateway -> cron ticker -> tick() ->
   _deliver_result(). (from NousResearch#3767)

2. HTML formatted_body: _send_matrix() now converts markdown to HTML
   using the optional markdown library, with h1-h6 to bold conversion
   for Element X compatibility. Falls back to plain text if markdown
   is not installed. Also adds random bytes to txn_id to prevent
   collisions. (from NousResearch#5236)

3. Origin fallback: when deliver="origin" but origin is null (jobs
   created via API/scripts), falls back to HOME_CHANNEL env vars
   in order: matrix -> telegram -> discord -> slack. (from NousResearch#2641)
angelburgosrosado pushed a commit to angelburgosrosado/hermes-agent that referenced this pull request Apr 27, 2026
…origin fallback

Salvaged from PRs NousResearch#3767 (chalkers), NousResearch#5236 (ygd58), NousResearch#2641 (buntingszn).

Three improvements to Matrix cron delivery:

1. Live adapter path: when the gateway is running, cron delivery now uses
   the connected MatrixAdapter via run_coroutine_threadsafe instead of
   the standalone HTTP PUT. This enables delivery to E2EE rooms where
   the raw HTTP path cannot encrypt. Falls back to standalone on failure.
   Threads adapters + event loop from gateway -> cron ticker -> tick() ->
   _deliver_result(). (from NousResearch#3767)

2. HTML formatted_body: _send_matrix() now converts markdown to HTML
   using the optional markdown library, with h1-h6 to bold conversion
   for Element X compatibility. Falls back to plain text if markdown
   is not installed. Also adds random bytes to txn_id to prevent
   collisions. (from NousResearch#5236)

3. Origin fallback: when deliver="origin" but origin is null (jobs
   created via API/scripts), falls back to HOME_CHANNEL env vars
   in order: matrix -> telegram -> discord -> slack. (from NousResearch#2641)
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…origin fallback

Salvaged from PRs NousResearch#3767 (chalkers), NousResearch#5236 (ygd58), NousResearch#2641 (buntingszn).

Three improvements to Matrix cron delivery:

1. Live adapter path: when the gateway is running, cron delivery now uses
   the connected MatrixAdapter via run_coroutine_threadsafe instead of
   the standalone HTTP PUT. This enables delivery to E2EE rooms where
   the raw HTTP path cannot encrypt. Falls back to standalone on failure.
   Threads adapters + event loop from gateway -> cron ticker -> tick() ->
   _deliver_result(). (from NousResearch#3767)

2. HTML formatted_body: _send_matrix() now converts markdown to HTML
   using the optional markdown library, with h1-h6 to bold conversion
   for Element X compatibility. Falls back to plain text if markdown
   is not installed. Also adds random bytes to txn_id to prevent
   collisions. (from NousResearch#5236)

3. Origin fallback: when deliver="origin" but origin is null (jobs
   created via API/scripts), falls back to HOME_CHANNEL env vars
   in order: matrix -> telegram -> discord -> slack. (from NousResearch#2641)
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…origin fallback

Salvaged from PRs NousResearch#3767 (chalkers), NousResearch#5236 (ygd58), NousResearch#2641 (buntingszn).

Three improvements to Matrix cron delivery:

1. Live adapter path: when the gateway is running, cron delivery now uses
   the connected MatrixAdapter via run_coroutine_threadsafe instead of
   the standalone HTTP PUT. This enables delivery to E2EE rooms where
   the raw HTTP path cannot encrypt. Falls back to standalone on failure.
   Threads adapters + event loop from gateway -> cron ticker -> tick() ->
   _deliver_result(). (from NousResearch#3767)

2. HTML formatted_body: _send_matrix() now converts markdown to HTML
   using the optional markdown library, with h1-h6 to bold conversion
   for Element X compatibility. Falls back to plain text if markdown
   is not installed. Also adds random bytes to txn_id to prevent
   collisions. (from NousResearch#5236)

3. Origin fallback: when deliver="origin" but origin is null (jobs
   created via API/scripts), falls back to HOME_CHANNEL env vars
   in order: matrix -> telegram -> discord -> slack. (from NousResearch#2641)
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…origin fallback

Salvaged from PRs NousResearch#3767 (chalkers), NousResearch#5236 (ygd58), NousResearch#2641 (buntingszn).

Three improvements to Matrix cron delivery:

1. Live adapter path: when the gateway is running, cron delivery now uses
   the connected MatrixAdapter via run_coroutine_threadsafe instead of
   the standalone HTTP PUT. This enables delivery to E2EE rooms where
   the raw HTTP path cannot encrypt. Falls back to standalone on failure.
   Threads adapters + event loop from gateway -> cron ticker -> tick() ->
   _deliver_result(). (from NousResearch#3767)

2. HTML formatted_body: _send_matrix() now converts markdown to HTML
   using the optional markdown library, with h1-h6 to bold conversion
   for Element X compatibility. Falls back to plain text if markdown
   is not installed. Also adds random bytes to txn_id to prevent
   collisions. (from NousResearch#5236)

3. Origin fallback: when deliver="origin" but origin is null (jobs
   created via API/scripts), falls back to HOME_CHANNEL env vars
   in order: matrix -> telegram -> discord -> slack. (from NousResearch#2641)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Cron delivery to Matrix sends plain text — no formatted_body or markdown rendering

3 participants