send_message email target recognition, plus unrelated Windows update fix
Cluster 34740. Data as of
Nothing open on GitHub. Fix merged in #35258 on 30 May 2026. Check it covers your case.
The short answer
- Known?
- Yes. 4 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #35258. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #35257. That is separate from the fix event. - Still open
- Nothing open on GitHub.
Three of four members concern send_message not recognizing email addresses as explicit targets (one bug report and two fix PRs). The fourth, about excluding launcher-shim ancestors in a Windows update concurrency check, appears unrelated to that theme.
Open issues0
Open PRs0
Fixes on record
-
#35257 fix(update/windows): robustly exclude launcher-shim ancestors from concurrent check
files:
hermes_cli/main.py,tests/hermes_cli/test_update_concurrent_quarantine.py -
#35258 fix(tools): recognize email addresses as explicit send_message targets
files:
tests/tools/test_send_message_tool.py,tools/send_message_tool.pyShow the diff
-
tests/tools/test_send_message_tool.py@@ -1220,6 +1220,90 @@ def test_slack_id_not_explicit_for_other_platforms(self): assert _parse_target_ref("telegram", "C0B0QV5434G")[2] is False +class TestParseTargetRefEmail: + """_parse_target_ref recognizes email addresses as explicit for the email platform.""" + + def test_standard_email_is_explicit(self): + chat_id, thread_id, is_explicit = _parse_target_ref("email", "[redacted:email]") + assert chat_id == "[redacted:email]" + assert thread_id is None + assert is_explicit is True + + def test_email_with_dots_in_local_part(self): + chat_id, _, is_explicit = _parse_target_ref("email", "[redacted:email]") + assert chat_id == "[redacted:email]" + assert is_explicit is True + + def test_email_with_plus_tag(self): + chat_id, _, is_explicit = _parse_target_ref("email", "[redacted:email]") + assert chat_id == "[redacted:email]" + assert is_explicit is True + + def test_email_strips_whitespace(self): + chat_id, _, is_explicit = _parse_target_ref("email", " [redacted:email] ") + assert chat_id == "[redacted:email]" + assert is_explicit is True + + def test_invalid_email_not_explicit(self): + assert _parse_target_ref("email", "not-an-email")[2] is False + assert _parse_target_ref("email", "@example.com")[2] is False + assert _parse_target_ref("email", "user@")[2] is False + assert _parse_target_ref("email", "[email protected]")[2] is False + + def test_email_not_explicit_for_other_platforms(self): + assert _parse_target_ref("telegram", "[redacted:email]")[2] is False + assert _parse_target_ref("discord", "[redacted:email]")[2] is False + assert _parse_target_ref("slack", "[redacted:email]")[2] is False + + +class TestEmailHomeChannelErrorHint: + """The no-home-channel error for email points at the real env var. + + Email reads its home channel from EMAIL_HOME_ADDRESS (gateway/config.py), + not the generic EMAIL_HOME_CHANNEL. The error guidance must name the + variable that is actually consulted so users who follow it succeed. + """ + + def test_email_error_names_email_home_address(self): + email_cfg = SimpleNamespace(enabled=True, token="", extra={}) + config = SimpleNamespace( + platforms={Platform.EMAIL: email_cfg}, + get_home_channel=lambda _platform: None, + ) + with patch("gateway.config.load_gateway_config", return_value=config), \ + patch("tools.interrupt.is_interrupted", return_value=False): + result = json.loads( + send_message_tool( + { + "action": "send", + "target": "email", + "message": "hi", + } + ) + ) + assert "EMAIL_HOME_ADDRESS" in result["error"] + assert "EMAIL_HOME_CHANNEL" not in result["error"] + + def test_non_email_platform_keeps_generic_home_channel_hint(self): + telegram_cfg = SimpleNamespace(enabled=True, token="***", extra={}) + config = SimpleNamespace( + platforms={Platform.TELEGRAM: telegram_cfg}, + get_home_channel=lambda _platform: None, + ) + with patch("gateway.config.load_gateway_config", return_value=config), \ + patch("tools.interrupt.is_interrupted", return_value=False): + result = json.loads( + send_message_tool( + { + "action": "send", + "target": "telegram", + "message": "hi", + } + ) + ) + assert "TELEGRAM_HOME_CHANNEL" in result["error"] + + class TestSendDiscordThreadId: """_send_discord uses thread_id when provided.""" -
tools/send_message_tool.py@@ -40,6 +40,15 @@ # downstream adapters (signal, etc.) expect. _PHONE_PLATFORMS = frozenset({"signal", "sms", "whatsapp"}) _E164_TARGET_RE = re.compile(r"^\s*\+(\d{7,15})\s*$") +# Email addresses — a valid email like "[redacted:email]" should be treated as +# an explicit target for the email platform, not fall through to channel-name +# resolution which has no way to resolve a raw address. +_EMAIL_TARGET_RE = re.compile(r"^\s*[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\s*$") +# Most platforms read their home channel from "<PLATFORM>_HOME_CHANNEL", but a +# few diverge. Email reads EMAIL_HOME_ADDRESS (see gateway/config.py), so the +# generic "<PLATFORM>_HOME_CHANNEL" hint would point users at a variable that is +# never read. Map the exceptions so the error guidance is actually actionable. +_HOME_CHANNEL_ENV_OVERRIDES = {"email": "EMAIL_HOME_ADDRESS"} _IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".gif"} _VIDEO_EXTS = {".mp4", ".mov", ".avi", ".mkv", ".3gp"} _AUDIO_EXTS = {".ogg", ".opus", ".mp3", ".wav", ".m4a", ".flac"} @@ -265,10 +274,13 @@ def _handle_send(args): chat_id = home.chat_id used_home_channel = True else: + home_env = _HOME_CHANNEL_ENV_OVERRIDES.get( + platform_name, f"{platform_name.upper()}_HOME_CHANNEL" + ) return json.dumps({ "error": f"No home channel set for {platform_name} to determine where to send the message. " f"Either specify a channel directly with '{platform_name}:CHANNEL_NAME', " - f"or set a home channel via: hermes config set {platform_name.upper()}_HOME_CHANNEL <channel_id>" + f"or set a home channel via: hermes config set {home_env} <channel_id>" }) duplicate_skip = _maybe_skip_cron_duplicate_send(platform_name, chat_id, thread_id) @@ -383,6 +395,10 @@ def _parse_target_ref(platform_name: str, target_ref: str): if target_ref.strip().isdigit(): return f"group:{target_ref.strip()}", None, True return None, None, False + if platform_name == "email": + match = _EMAIL_TARGET_RE.fullmatch(target_ref) + if match: + return target_ref.strip(), None, True if platform_name in _PHONE_PLATFORMS: match = _E164_TARGET_RE.fullmatch(target_ref) if match:
-
1 PR closed without merging
From the close comments: merged via another change 1
-
#34748 fix(tools): recognize email addresses as explicit targets in send_message · merged via another change →
d3724c0,#35258
Closed issues · 1
- Closed#34740[Bug]: send_message does not recognize explicit email targets and incorrectly falls back to home target resolution
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab