{"repo":"NousResearch/hermes-agent","public_id":"19572","display_label":"Suppress noisy gateway status messages, config flags","summary":"These issues and PRs concern suppressing internal operational noise (status updates, retry messages, lifecycle events) from leaking to users across chat gateways (WhatsApp, Discord, Slack, Signal, Telegram), plus related config options and some compression/retry fixes.","subsystem":"agent","counts":{"members":11,"open_issues":2,"open_issues_distinct":2,"open_prs":1,"closed_issues":4,"merged_prs":1,"attempt_prs":4},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":53316},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-06-26T23:17:11+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.14181818181818184},"fix_edges":[{"issue_number":39293,"pr_number":53316,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":19572,"title":"Add config flag to suppress lifecycle status messages on messaging gateways","url":null,"state":"open"},{"kind":"issue","number":27131,"title":"[Feature] Add config option to suppress fallback/rate-limit status messages from gateway channel","url":null,"state":"closed"},{"kind":"issue","number":39293,"title":"[Bug]: Gateway status/error sanitization is Telegram-only — WhatsApp/Discord/Slack/Signal leak internal noise to users","url":null,"state":"closed"},{"kind":"issue","number":60604,"title":"[Bug]: compact context fail","url":null,"state":"open"},{"kind":"issue","number":63554,"title":"Add config flags to suppress '⏳ Working' and '💾 Self-improvement review' system messages on chat platforms","url":null,"state":"closed"},{"kind":"issue","number":63555,"title":"Add config flags to suppress 'Working' and 'Self-improvement review' system messages on chat platforms","url":null,"state":"closed"},{"kind":"pr","number":20040,"title":"fix(gateway): gate lifecycle status messages","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":27134,"title":"feat(logging): add config option to suppress fallback/rate-limit status messages","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#4242","number":4242},{"kind":"pr","value":"#4242","number":4242},{"kind":"pr","value":"#4242","number":4242},{"kind":"pr","value":"#4242","number":4242},{"kind":"pr","value":"#4242","number":4242}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":39323,"title":"fix(gateway): suppress operational status/error noise on all chat gateways, not just Telegram (#39293)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"57864d07e","number":0},{"kind":"commit","value":"c377e954f","number":0},{"kind":"pr","value":"#53316","number":53316}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":53316,"title":"fix(gateway): suppress operational status/error noise on all chat gateways (#39293)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["gateway/run.py","tests/gateway/test_telegram_noise_filter.py"],"fix_hunks":[{"file":"gateway/run.py","hunks":["@@ -87,6 +87,22 @@\n     re.IGNORECASE | re.DOTALL,\n )\n \n+# Surfaces that consume gateway text programmatically (CLI/TUI \"local\"\n+# diagnostics, API JSON, webhook payloads) and therefore must keep RAW\n+# status/error text. EVERY other platform is a human-facing chat surface\n+# where operational lifecycle/provider-error noise (and any secrets in it)\n+# must be suppressed or sanitized. Widens #28533's Telegram-only filter to\n+# all chat gateways (#39293). Fail-closed: unknown/empty platform -> chat.\n+_GATEWAY_RAW_TEXT_PLATFORMS = frozenset(\n+    {\"local\", \"api_server\", \"webhook\", \"msgraph_webhook\"}\n+)\n+\n+\n+def _gateway_surface_passes_raw_text(platform: Any) -> bool:\n+    \"\"\"True only for programmatic/local surfaces that must keep raw text.\"\"\"\n+    return _gateway_platform_value(platform) in _GATEWAY_RAW_TEXT_PLATFORMS\n+\n+\n _GATEWAY_PROVIDER_ERROR_RE = re.compile(\n     r\"(\"  # infrastructure/provider error preambles, not ordinary assistant prose\n     r\"api\\s+(?:call\\s+)?failed\"","@@ -370,15 +386,18 @@ def _looks_like_gateway_provider_error(text: str) -> bool:\n \n \n def _sanitize_gateway_final_response(platform: Any, text: str) -> str:\n-    \"\"\"Sanitize final gateway replies before sending them to high-noise chats.\n-\n-    Telegram is Bob's mobile inbox, so it should receive concise, safe provider\n-    failure categories instead of raw HTTP bodies, request IDs, or policy text.\n-    Other platforms keep the existing behaviour for now.\n+    \"\"\"Sanitize final gateway replies before sending them to chat surfaces.\n+\n+    Every human-facing chat surface (Telegram, WhatsApp, Discord, Slack,\n+    Signal, Matrix, plugin platforms, etc.) should receive concise, safe\n+    provider failure categories with secrets redacted instead of raw HTTP\n+    bodies, request IDs, leaked credentials, or policy text. Only programmatic\n+    surfaces in ``_GATEWAY_RAW_TEXT_PLATFORMS`` (CLI/TUI ``local`` diagnostics,\n+    API JSON, webhook payloads) keep the raw text unchanged.\n     \"\"\"\n     if not text:\n         return text\n-    if _gateway_platform_value(platform) != \"telegram\":\n+    if _gateway_surface_passes_raw_text(platform):\n         return text\n \n     redacted = _redact_gateway_user_facing_secrets(str(text))","@@ -392,7 +411,7 @@ def _prepare_gateway_status_message(platform: Any, event_type: str, message: str\n     text = str(message or \"\").strip()\n     if not text:\n         return None\n-    if _gateway_platform_value(platform) != \"telegram\":\n+    if _gateway_surface_passes_raw_text(platform):\n         return text\n \n     text = _redact_gateway_user_facing_secrets(text)"]},{"file":"tests/gateway/test_telegram_noise_filter.py","hunks":["@@ -1,4 +1,6 @@\n-\"\"\"Telegram-specific gateway filtering for noisy status/error output.\"\"\"\n+\"\"\"Gateway noise/secret filtering across chat surfaces (Telegram + siblings).\"\"\"\n+\n+import pytest\n \n from gateway.config import Platform\n from gateway.run import (","@@ -6,6 +8,34 @@\n     _sanitize_gateway_final_response,\n )\n \n+# Every human-facing chat surface that must receive noise-filtered,\n+# secret-redacted, provider-error-sanitized output (not just Telegram).\n+CHAT_PLATFORMS = [\n+    \"telegram\",\n+    \"whatsapp\",\n+    \"discord\",\n+    \"slack\",\n+    \"signal\",\n+    \"matrix\",\n+    \"mattermost\",\n+    \"dingtalk\",\n+    \"feishu\",\n+    \"wecom\",\n+    \"weixin\",\n+    \"bluebubbles\",\n+    \"qqbot\",\n+    \"homeassistant\",\n+    \"sms\",\n+]\n+\n+NOISY_STATUS_MESSAGES = [\n+    \"🗜️ Preflight compression check before sending...\",\n+    \"🗜️ Compacting context — summarizing earlier conversation so I can continue...\",\n+    \"⚠ Compression summary failed: upstream error. Inserted a fallback context marker.\",\n+    \"⏱️ Rate limited. Waiting 30.0s (attempt 2/3)...\",\n+    \"⏳ Retrying in 4.2s (attempt 1/3)...\",\n+]\n+\n \n def test_telegram_status_suppresses_auxiliary_and_retry_noise():\n     \"\"\"Auxiliary failures and retry backoff chatter should not hit Telegram.\"\"\"","@@ -23,12 +53,91 @@ def test_telegram_status_suppresses_auxiliary_and_retry_noise():\n         assert _prepare_gateway_status_message(Platform.TELEGRAM, \"warn\", message) is None\n \n \n-def test_non_telegram_status_is_unchanged():\n-    \"\"\"The Telegram quieting policy must not hide CLI/Discord diagnostics.\"\"\"\n+def test_programmatic_surfaces_keep_raw_status():\n+    \"\"\"Programmatic surfaces (local/api/webhook) must keep raw diagnostics.\n+\n+    Negative case for the invariant: the chat-noise filter must not touch\n+    CLI/TUI diagnostics, API JSON, or webhook payloads.\n+    \"\"\"\n     message = \"⏳ Retrying in 4.2s (attempt 1/3)...\"\n \n-    assert _prepare_gateway_status_message(Platform.DISCORD, \"lifecycle\", message) == message\n-    assert _prepare_gateway_status_message(\"local\", \"lifecycle\", message) == message\n+    for platform in (\"local\", \"api_server\", \"webhook\", \"msgraph_webhook\"):\n+        assert (\n+            _prepare_gateway_status_message(platform, \"lifecycle\", message) == message\n+        )\n+\n+\n[redacted:email](\"platform\", CHAT_PLATFORMS)\n[redacted:email](\"message\", NOISY_STATUS_MESSAGES)\n+def test_all_chat_gateways_suppress_noise(platform, message):\n+    \"\"\"Operational lifecycle/retry noise must be suppressed on every chat surface.\"\"\"\n+    assert _prepare_gateway_status_message(platform, \"warn\", message) is None\n+\n+\n[redacted:email](\"platform\", [\"whatsapp\", \"slack\", \"signal\", \"matrix\"])\n+def test_chat_gateways_redact_secret_in_provider_error(platform):\n+    \"\"\"Provider-error bodies carrying secrets must never reach chat users.\n+\n+    THE security invariant being widened from Telegram (#28533) to all chat\n+    surfaces (#39293): a leaked bearer token in a provider error body must be\n+    redacted/replaced before delivery on any chat platform.\n+    \"\"\"\n+    raw = (\n+        \"API call failed after 3 retries: HTTP 401 Unauthorized — \"\n+        \"Authorization: Bearer [redacted:openai-key]\"\n+    )\n+\n+    sanitized = _sanitize_gateway_final_response(platform, raw)\n+\n+    assert \"[redacted:openai-key]\" not in sanitized\n+    assert \"sk-ABCDEF\" not in sanitized\n+    assert \"HTTP 401\" not in sanitized\n+    # The user gets the safe provider-error category instead of the raw body.\n+    assert \"provider\" in sanitized.lower()\n+\n+\n[redacted:email](\"platform\", [\"whatsapp\", \"slack\", \"signal\", \"matrix\"])\n+def test_chat_gateways_redact_secret_in_non_error_body(platform):\n+    \"\"\"Secrets must be redacted even when no provider-error rewrite fires.\n+\n+    The provider-error case above is rewritten wholesale to a generic\n+    category string, so it cannot, on its own, prove the secret-redaction\n+    layer works — the rewrite would strip the body regardless. This case\n+    feeds ordinary assistant prose that merely *echoes* a bearer token (not\n+    a provider-error envelope), so `_redact_gateway_user_facing_secrets` is\n+    the only thing standing between the token and the user. Removing the\n+    redaction patterns makes this fail (genuine regression guard); the\n+    surrounding prose must survive intact.\n+    \"\"\"\n+    raw = (\n+        \"Sure — here is the example request you asked for: \"\n+        \"curl -H 'Authorization: Bearer [redacted:openai-key]' \"\n+        \"https://api.example.com/v1/models\"\n+    )\n+\n+    sanitized = _sanitize_gateway_final_response(platform, raw)\n+\n+    assert \"[redacted:openai-key]\" not in sanitized\n+    assert \"sk-ABCDEF\" not in sanitized\n+    assert \"[REDACTED]\" in sanitized\n+    # Non-secret prose is preserved — redaction is surgical, not a wholesale\n+    # rewrite, on bodies that are not provider-error envelopes.\n+    assert \"here is the example request you asked for\" in sanitized\n+\n+\n+def test_plugin_platform_string_suppresses_noise():\n+    \"\"\"Unknown/plugin chat platforms fail closed to the chat-filter path.\"\"\"\n+    message = \"⏳ Retrying in 4.2s (attempt 1/3)...\"\n+\n+    assert _prepare_gateway_status_message(\"irc\", \"warn\", message) is None\n+\n+\n[redacted:email](\"platform\", CHAT_PLATFORMS)\n+def test_chat_gateways_keep_normal_answers(platform):\n+    \"\"\"Normal assistant content must pass through unchanged on chat surfaces.\"\"\"\n+    answer = \"Here is the clean summary you asked for.\"\n+\n+    assert _sanitize_gateway_final_response(platform, answer) == answer\n \n \n def test_telegram_status_sanitizes_raw_provider_security_errors():\n"]}]},{"kind":"pr","number":60744,"title":"fix(compression): raise timeout floor from 300s to 900s for slow local models (#60604)","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}