Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions gateway/platforms/discord.py
Original file line number Diff line number Diff line change
Expand Up @@ -2725,8 +2725,15 @@ def _discord_free_response_channels(self) -> set:
raw = os.getenv("DISCORD_FREE_RESPONSE_CHANNELS", "")
if isinstance(raw, list):
return {str(part).strip() for part in raw if str(part).strip()}
if isinstance(raw, str) and raw.strip():
return {part.strip() for part in raw.split(",") if part.strip()}
# Coerce non-list scalars (str/int/float) to str before splitting.
# YAML parses a bare numeric value such as
# `free_response_channels: 1491973769726791812` as int, which was
# previously falling through the isinstance(str) branch and silently
# returning an empty set. str() here accepts whatever scalar the YAML
# loader hands us without changing existing string/CSV semantics.
s = str(raw).strip() if raw is not None else ""
if s:
return {part.strip() for part in s.split(",") if part.strip()}
return set()

def _thread_parent_channel(self, channel: Any) -> Any:
Expand Down
11 changes: 9 additions & 2 deletions gateway/platforms/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -1739,6 +1739,13 @@ def _slack_free_response_channels(self) -> set:
raw = os.getenv("SLACK_FREE_RESPONSE_CHANNELS", "")
if isinstance(raw, list):
return {str(part).strip() for part in raw if str(part).strip()}
if isinstance(raw, str) and raw.strip():
return {part.strip() for part in raw.split(",") if part.strip()}
# Coerce non-list scalars (str/int/float) to str before splitting.
# A bare numeric YAML value (`free_response_channels: 1234567890`) is
# loaded as int and was previously falling through the isinstance(str)
# branch to return an empty set. str() here accepts whatever scalar
# the YAML loader hands us without changing existing string/CSV
# semantics.
s = str(raw).strip() if raw is not None else ""
if s:
return {part.strip() for part in s.split(",") if part.strip()}
return set()
20 changes: 20 additions & 0 deletions tests/gateway/test_discord_free_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,26 @@ async def test_discord_free_response_channel_can_come_from_config_extra(adapter,
assert event.text == "allowed from config"


def test_discord_free_response_channels_bare_int(adapter, monkeypatch):
# YAML `discord.free_response_channels: 1491973769726791812` (single bare
# integer) is loaded as an int and previously fell through the
# isinstance(str) branch in _discord_free_response_channels, silently
# returning an empty set. Scalar → str coercion makes single-channel
# config work without having to quote the ID in YAML.
monkeypatch.delenv("DISCORD_FREE_RESPONSE_CHANNELS", raising=False)
adapter.config.extra["free_response_channels"] = 1491973769726791812

assert adapter._discord_free_response_channels() == {"1491973769726791812"}


def test_discord_free_response_channels_int_list(adapter, monkeypatch):
# YAML list form with bare numeric entries — each element should be coerced.
monkeypatch.delenv("DISCORD_FREE_RESPONSE_CHANNELS", raising=False)
adapter.config.extra["free_response_channels"] = [1491973769726791812, 99999]

assert adapter._discord_free_response_channels() == {"1491973769726791812", "99999"}


@pytest.mark.asyncio
async def test_discord_forum_parent_in_free_response_list_allows_forum_thread(adapter, monkeypatch):
monkeypatch.setenv("DISCORD_REQUIRE_MENTION", "true")
Expand Down
17 changes: 17 additions & 0 deletions tests/gateway/test_slack_mention.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,23 @@ def test_free_response_channels_env_var_fallback(monkeypatch):
assert OTHER_CHANNEL_ID in result


def test_free_response_channels_bare_int():
# YAML `free_response_channels: 1491973769726791812` (single bare integer)
# is loaded as an int and would previously fall through the isinstance(str)
# branch to return an empty set. Coerce scalar → str so single-channel
# config without quoting works as users expect.
adapter = _make_adapter(free_response_channels=1491973769726791812)
result = adapter._slack_free_response_channels()
assert result == {"1491973769726791812"}


def test_free_response_channels_int_list():
# YAML list form with bare numeric entries — each element should be coerced.
adapter = _make_adapter(free_response_channels=[1491973769726791812, 99999])
result = adapter._slack_free_response_channels()
assert result == {"1491973769726791812", "99999"}


# ---------------------------------------------------------------------------
# Tests: mention gating integration (simulating _handle_slack_message logic)
# ---------------------------------------------------------------------------
Expand Down