From 5cdc39e29a032091c4989045b0843715737680c3 Mon Sep 17 00:00:00 2001 From: nightq Date: Sat, 18 Apr 2026 09:55:21 +0800 Subject: [PATCH] fix(gateway): preserve case-sensitive chat IDs in DeliveryTarget.parse Fixes NousResearch/hermes-agent#11768 Root cause: target.strip().lower() was lowercasing the entire target string, corrupting case-sensitive chat IDs like Slack C123ABC and Matrix !RoomABC. Fix: Only lowercase the platform prefix for case-insensitive matching; preserve the original case for chat_id and thread_id values. --- gateway/delivery.py | 16 ++++++---- tests/gateway/test_delivery.py | 58 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/gateway/delivery.py b/gateway/delivery.py index bc901c2a..41a25c56 100644 --- a/gateway/delivery.py +++ b/gateway/delivery.py @@ -53,9 +53,10 @@ class DeliveryTarget: - "telegram" → Telegram home channel - "telegram:123456" → specific Telegram chat """ - target = target.strip().lower() + target_stripped = target.strip() + target_lower = target_stripped.lower() - if target == "origin": + if target_lower == "origin": if origin: return cls( platform=origin.platform, @@ -67,13 +68,14 @@ class DeliveryTarget: # Fallback to local if no origin return cls(platform=Platform.LOCAL, is_origin=True) - if target == "local": + if target_lower == "local": return cls(platform=Platform.LOCAL) # Check for platform:chat_id or platform:chat_id:thread_id format - if ":" in target: - parts = target.split(":", 2) - platform_str = parts[0] + # Use the original case for chat_id/thread_id to preserve case-sensitive IDs + if ":" in target_stripped: + parts = target_stripped.split(":", 2) + platform_str = parts[0].lower() # Platform names are case-insensitive chat_id = parts[1] if len(parts) > 1 else None thread_id = parts[2] if len(parts) > 2 else None try: @@ -85,7 +87,7 @@ class DeliveryTarget: # Just a platform name (use home channel) try: - platform = Platform(target) + platform = Platform(target_lower) return cls(platform=platform) except ValueError: # Unknown platform, treat as local diff --git a/tests/gateway/test_delivery.py b/tests/gateway/test_delivery.py index 9501045d..36422312 100644 --- a/tests/gateway/test_delivery.py +++ b/tests/gateway/test_delivery.py @@ -65,4 +65,62 @@ class TestTargetToStringRoundtrip: assert reparsed.chat_id == "999" +class TestCaseSensitiveChatIdParsing: + """Test that chat IDs preserve their original case (issue #11768).""" + + def test_slack_uppercase_chat_id_preserved(self): + """Slack channel IDs like C123ABC should preserve case.""" + target = DeliveryTarget.parse("slack:C123ABC") + assert target.platform == Platform.SLACK + assert target.chat_id == "C123ABC" # Should NOT be lowercased to c123abc + assert target.is_explicit is True + + def test_slack_chat_id_with_thread_preserved(self): + """Slack channel:thread IDs should preserve case.""" + target = DeliveryTarget.parse("slack:C123ABC:thread123") + assert target.platform == Platform.SLACK + assert target.chat_id == "C123ABC" + assert target.thread_id == "thread123" + + def test_matrix_room_id_preserved(self): + """Matrix room IDs like !RoomABC:example.org should preserve case. + + Note: Matrix room IDs contain colons (e.g., !RoomABC:example.org). + Due to the platform:chat_id:thread_id format, these are parsed as + chat_id=!RoomABC and thread_id=example.org. This is a known limitation + of the current format. The fix preserves case but doesn't change the + parsing structure. + """ + target = DeliveryTarget.parse("matrix:!RoomABC:example.org") + assert target.platform == Platform.MATRIX + # The room ID is split at the first colon after the platform prefix + # This is a format limitation - the case is preserved but the structure is split + assert target.chat_id == "!RoomABC" + assert target.thread_id == "example.org" + + def test_mixed_case_chat_id_roundtrip(self): + """Mixed-case chat IDs should survive parse-to_string roundtrip.""" + original = "telegram:ChatId123ABC" + target = DeliveryTarget.parse(original) + s = target.to_string() + reparsed = DeliveryTarget.parse(s) + assert reparsed.chat_id == "ChatId123ABC" + + +class TestPlatformNameCaseInsensitivity: + """Test that platform names are case-insensitive.""" + + def test_uppercase_platform_name(self): + """Platform names should be case-insensitive.""" + target = DeliveryTarget.parse("TELEGRAM:12345") + assert target.platform == Platform.TELEGRAM + assert target.chat_id == "12345" + + def test_mixed_case_platform_name(self): + """Mixed-case platform names should work.""" + target = DeliveryTarget.parse("TeleGram:12345") + assert target.platform == Platform.TELEGRAM + assert target.chat_id == "12345" + +