{"repo":"NousResearch/hermes-agent","public_id":"35611","display_label":"Fix redaction masking Discord mention snowflake IDs","summary":"These items address a bug where the redact_sensitive_text() function incorrectly masks Discord @-mention snowflake IDs, breaking multi-bot mentions. The work focuses on stopping this unintended masking at the persistence boundary.","subsystem":"agent","counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":1,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":35665,"merged_at":"2026-05-31T03:48:41+00:00"},"resolution":{"state":"resolved","by":"pr-35665"},"freshness":{"state":"verified","last_verified_at":"2026-05-31T03:48:41+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":35611,"pr_number":35665,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-31T03:48:41+00:00"}],"members":[{"kind":"issue","number":35611,"title":"[Bug]: redact_sensitive_text() unconditionally masks Discord <@snowflake> mentions, breaking multi-bot @-pings","url":null,"state":"closed"},{"kind":"pr","number":35615,"title":"fix(redact): stop masking Discord mention snowflake IDs at persistence boundary","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#32259","number":32259},{"kind":"pr","value":"#35611","number":35611}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":35665,"title":"fix(redact): stop masking Discord mention snowflakes","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/redact.py","tests/agent/test_redact.py"],"fix_hunks":[{"file":"agent/redact.py","hunks":["@@ -150,10 +150,6 @@\n     r\"(?:\\.[A-Za-z0-9_=-]{4,}){0,2}\"   # Optional payload and/or signature\n )\n \n-# Discord user/role mentions: <@123456789012345678> or <@!123456789012345678>\n-# Snowflake IDs are 17-20 digit integers that resolve to specific Discord accounts.\n-_DISCORD_MENTION_RE = re.compile(r\"<@!?(\\d{17,20})>\")\n-\n # E.164 phone numbers: +<country><number>, 7-15 digits\n # Negative lookahead prevents matching hex strings or identifiers\n _SIGNAL_PHONE_RE = re.compile(r\"(\\+[1-9]\\d{6,14})(?![A-Za-z0-9])\")","@@ -419,10 +415,6 @@ def _redact_telegram(m):\n     if \"&\" in text and \"=\" in text:\n         text = _redact_form_body(text)\n \n-    # Discord user/role mentions (<@snowflake_id>)\n-    if \"<@\" in text:\n-        text = _DISCORD_MENTION_RE.sub(lambda m: f\"<@{'!' if '!' in m.group(0) else ''}***>\", text)\n-\n     # E.164 phone numbers (Signal, WhatsApp)\n     if \"+\" in text:\n         def _redact_phone(m):"]},{"file":"tests/agent/test_redact.py","hunks":["@@ -342,39 +342,33 @@ def test_home_assistant_jwt_in_memory(self):\n \n \n class TestDiscordMentions:\n-    \"\"\"Discord snowflake IDs in <@ID> or <@!ID> format.\"\"\"\n+    \"\"\"Discord mention snowflakes (<@ID> / <@!ID>) are public syntax, not\n+    secrets — they must pass through the redactor unchanged so multi-bot\n+    @-pings (DISCORD_ALLOW_BOTS=mentions) keep resolving. See issue #35611.\"\"\"\n \n-    def test_normal_mention(self):\n-        result = redact_sensitive_text(\"Hello <@222589316709220353>\")\n-        assert \"222589316709220353\" not in result\n-        assert \"<@***>\" in result\n+    def test_normal_mention_passes_through(self):\n+        text = \"Hello <@222589316709220353>\"\n+        assert redact_sensitive_text(text) == text\n \n-    def test_nickname_mention(self):\n-        result = redact_sensitive_text(\"Ping <@!1331549159177846844>\")\n-        assert \"1331549159177846844\" not in result\n-        assert \"<@!***>\" in result\n+    def test_nickname_mention_passes_through(self):\n+        text = \"Ping <@!1331549159177846844>\"\n+        assert redact_sensitive_text(text) == text\n \n-    def test_multiple_mentions(self):\n+    def test_multiple_mentions_pass_through(self):\n         text = \"<@111111111111111111> and <@222222222222222222>\"\n-        result = redact_sensitive_text(text)\n-        assert \"111111111111111111\" not in result\n-        assert \"222222222222222222\" not in result\n+        assert redact_sensitive_text(text) == text\n \n-    def test_short_id_not_matched(self):\n-        \"\"\"IDs shorter than 17 digits are not Discord snowflakes.\"\"\"\n+    def test_short_id_passes_through(self):\n         text = \"<@12345>\"\n         assert redact_sensitive_text(text) == text\n \n-    def test_slack_mention_not_matched(self):\n-        \"\"\"Slack mentions use letters, not pure digits.\"\"\"\n+    def test_slack_mention_passes_through(self):\n         text = \"<@U024BE7LH>\"\n         assert redact_sensitive_text(text) == text\n \n     def test_preserves_surrounding_text(self):\n         text = \"User <@222589316709220353> said hello\"\n-        result = redact_sensitive_text(text)\n-        assert result.startswith(\"User \")\n-        assert result.endswith(\" said hello\")\n+        assert redact_sensitive_text(text) == text\n \n \n class TestWebUrlsNotRedacted:\n"]}]},{"kind":"pr","number":35929,"title":"fix(gateway): mention-lint guardrail for Discord <@...> literals","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"c2cbe2c97","number":0},{"kind":"pr","value":"#35611","number":35611},{"kind":"pr","value":"#35665","number":35665},{"kind":"pr","value":"#125","number":125}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}