mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(slack): throttle channel directory warnings
This commit is contained in:
parent
e027f43f70
commit
42534605b7
2 changed files with 60 additions and 9 deletions
|
|
@ -9,6 +9,7 @@ action="list" and for resolving human-friendly channel names to numeric IDs.
|
|||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
|
@ -18,6 +19,14 @@ from utils import atomic_json_write
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
DIRECTORY_PATH = get_hermes_home() / "channel_directory.json"
|
||||
# Throttle window for repeated Slack channel-directory refresh failures.
|
||||
# The directory rebuilds on a timer, so a persistent workspace error (e.g.
|
||||
# missing scope, revoked token) would otherwise re-log the same warning on
|
||||
# every refresh. Warn once per (team, error detail) per interval; repeats
|
||||
# drop to DEBUG.
|
||||
_SLACK_DIRECTORY_WARNING_INTERVAL_SECONDS = 3600
|
||||
_slack_directory_warning_last: Dict[tuple[str, str], float] = {}
|
||||
|
||||
# User-maintained friendly-name overlay. The directory is fully regenerated
|
||||
# from live adapters + session data on a timer, so hand-edits to
|
||||
# channel_directory.json don't survive. Aliases declared here are re-applied
|
||||
|
|
@ -105,6 +114,27 @@ def _session_entry_name(origin: Dict[str, Any]) -> str:
|
|||
return f"{base_name} / {topic_label}"
|
||||
|
||||
|
||||
def _warn_slack_directory(team_id: str, detail: str) -> None:
|
||||
"""Warn once per team/error per interval for recurring Slack refresh failures."""
|
||||
key = (str(team_id), str(detail))
|
||||
now = time.monotonic()
|
||||
last = _slack_directory_warning_last.get(key)
|
||||
if last is None or now - last >= _SLACK_DIRECTORY_WARNING_INTERVAL_SECONDS:
|
||||
_slack_directory_warning_last[key] = now
|
||||
logger.warning(
|
||||
"Channel directory: failed to list Slack channels for team %s: %s",
|
||||
team_id,
|
||||
detail,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"Channel directory: suppressed repeated Slack channel list failure "
|
||||
"for team %s: %s",
|
||||
team_id,
|
||||
detail,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build / refresh
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -240,11 +270,8 @@ async def _build_slack(adapter) -> List[Dict[str, Any]]:
|
|||
cursor=cursor,
|
||||
)
|
||||
if not response.get("ok"):
|
||||
logger.warning(
|
||||
"Channel directory: users.conversations not ok for team %s: %s",
|
||||
team_id,
|
||||
response.get("error", "unknown"),
|
||||
)
|
||||
detail = f"users.conversations not ok: {response.get('error', 'unknown')}"
|
||||
_warn_slack_directory(team_id, detail)
|
||||
break
|
||||
for ch in response.get("channels", []):
|
||||
cid = ch.get("id")
|
||||
|
|
@ -261,10 +288,7 @@ async def _build_slack(adapter) -> List[Dict[str, Any]]:
|
|||
if not cursor:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Channel directory: failed to list Slack channels for team %s: %s",
|
||||
team_id, e,
|
||||
)
|
||||
_warn_slack_directory(team_id, str(e))
|
||||
continue
|
||||
|
||||
# Merge in DM/group entries discovered from session history.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from gateway.channel_directory import (
|
|||
_apply_channel_aliases,
|
||||
_build_from_sessions,
|
||||
_build_slack,
|
||||
_slack_directory_warning_last,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -576,6 +577,32 @@ class TestBuildSlack:
|
|||
|
||||
assert entries == []
|
||||
|
||||
def test_repeated_workspace_errors_are_warning_throttled(
|
||||
self, tmp_path, caplog, monkeypatch
|
||||
):
|
||||
client = _make_slack_client([
|
||||
{"ok": False, "error": "missing_scope"},
|
||||
{"ok": False, "error": "missing_scope"},
|
||||
])
|
||||
_slack_directory_warning_last.clear()
|
||||
monkeypatch.setattr("gateway.channel_directory.time.monotonic", lambda: 1000.0)
|
||||
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}), caplog.at_level("DEBUG"):
|
||||
asyncio.run(_build_slack(_make_slack_adapter({"T1": client})))
|
||||
asyncio.run(_build_slack(_make_slack_adapter({"T1": client})))
|
||||
|
||||
warning_messages = [
|
||||
record.getMessage()
|
||||
for record in caplog.records
|
||||
if record.levelname == "WARNING"
|
||||
]
|
||||
assert len(warning_messages) == 1
|
||||
assert "missing_scope" in warning_messages[0]
|
||||
assert any(
|
||||
"suppressed repeated Slack channel list failure" in record.getMessage()
|
||||
for record in caplog.records
|
||||
)
|
||||
|
||||
|
||||
class TestChannelAliases:
|
||||
"""The user-maintained alias overlay (channel_aliases.json) gives durable
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue