Skip to content

fix(discord): scope auto_thread/reactions/allow_mentions/reply_to_mode per profile - #100435

Closed
nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/discord-multiplex-config-scope
Closed

nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/discord-multiplex-config-scope

Conversation

@nftpoetrist

Copy link
Copy Markdown

What & why

_apply_yaml_config()'s YAML→env bridge for auto_thread, reactions, allow_mentions.*, and reply_to_mode writes to process-global os.environ unconditionally (first-writer-wins, no _skip_env_bridge check) — unlike the auth-gate keys (allow_from, allowed_roles, allow_all_users, free_response_channels, ignored_channels, allowed_channels, no_thread_channels) already fixed for this exact bug class under issue #72348.

Under gateway.multiplex_profiles, a secondary profile's own config.yaml settings leak into shared env for every other Discord adapter in the process to inherit. Worst case: allow_mentions.everyone: true on one profile silently lets every profile's bot ping @everyone/@here on its own server — Discord bots default to parsing those pings, and this adapter's own docstring explains the safe-default exists specifically to stop LLM output/echoed content from doing that. reply_to_mode is the reverse-direction case: a secondary profile's own setting leaks into the (unscoped) default profile's read.

The read sides (_reactions_enabled, the inline auto_thread check in _handle_message, _build_allowed_mentions) also read raw os.getenv/os.environ directly with no per-profile fallback at all.

Fix

Reuses the established per-profile machinery from issue #72348 instead of inventing a new mechanism:

  • _apply_yaml_config: gates the auto_thread/reactions/allow_mentions/reply_to_mode env writes with the existing _skip_env_bridge check (_profile_scoped_config_load()), and seeds auto_thread/reactions/allow_mentions into PlatformConfig.extra (they weren't seeded at all before, so a scoped profile's own YAML value would otherwise have nowhere to go).
  • _reactions_enabled / new _auto_thread_enabled: use the existing self._gate_raw() per-profile accessor (the same one _get_allowed_channels etc. already use) instead of raw os.getenv, and add both env vars to _GATE_ENV_KEYS so they're captured in the connect()-time per-profile snapshot.
  • _build_allowed_mentions: accepts an optional extra param (the call site now passes self.config.extra), and uses _scoped_gate_env (the same helper the gate accessors are built on) instead of raw os.getenv for each of the four DISCORD_ALLOW_MENTION_* flags, falling back to extra["allow_mentions"] when unset. Existing bare _build_allowed_mentions() calls (tests, and any future caller with no adapter instance) are unaffected — extra defaults to None, preserving the exact prior env-only behavior.

Tests

Extended tests/plugins/platforms/test_discord_gate_isolation.py (the established test file for issue #72348's bug class) with 16 new tests mirroring its existing conventions (two-adapter isolation, snapshot-vs-extra precedence, env-does-not-leak-into-snapshotted-adapter, and _apply_yaml_config scoped-vs-single-profile write behavior).

  • tests/plugins/platforms/test_discord_gate_isolation.py — 37 passed
  • tests/gateway/test_discord_*.py, tests/tools/test_discord_*.py, tests/hermes_cli/test_discord_*.py, tests/plugins/test_discord_*.py (full Discord suite) — 346 passed, 3 pre-existing failures in test_discord_send.py confirmed independent of this change (pass 11/11 when run standalone, both with and without this fix — a test-order-pollution artifact of running the whole suite together, not a regression)
  • Mutation-verify: reverting the fix makes 12 of the 16 new tests fail as expected (the other 4 exercise the unscoped/single-profile path, which was never buggy and correctly stays green)
  • Needed a temporary uv pip install "discord.py[voice]==2.7.1" per the standing optional-dependency gotcha, to get real dynamic verification instead of importorskip skips on the AllowedMentions-dependent tests

Competitor check

Searched DISCORD_AUTO_THREAD, DISCORD_REACTIONS, DISCORD_ALLOW_MENTION, DISCORD_REPLY_TO_MODE, "discord multiplex allow_mentions" (all PR/issue states) — no PR targets this scoping bug. One open PR (#68473) touches the same _handle_message region (adds a logger.debug call in the neighboring require_mention drop path) — different lines, different concern, no overlap.

…e per profile

_apply_yaml_config()'s YAML->env bridge for auto_thread, reactions,
allow_mentions.*, and reply_to_mode wrote to process-global os.environ
unconditionally (first-writer-wins, no _skip_env_bridge check), unlike the
auth-gate keys (allow_from, allowed_roles, allow_all_users,
free_response_channels, ignored_channels, allowed_channels,
no_thread_channels) already fixed for this under issue NousResearch#72348. Under
gateway.multiplex_profiles, a secondary profile's own config.yaml settings
would leak into shared env for every other Discord adapter in the process
to inherit — most notably allow_mentions.everyone: true, which would let
every profile's bot ping @everyone/@here on its own server. reply_to_mode
is the reverse-direction case: a secondary profile's own setting leaking
into the (unscoped) default profile's read.

The read sides (_reactions_enabled, the inline auto_thread check in
_handle_message, _build_allowed_mentions) also read raw os.getenv/os.environ
directly with no per-profile fallback at all.

Fixes both directions, reusing established per-profile machinery instead of
inventing a new mechanism:
- _apply_yaml_config: gate the auto_thread/reactions/allow_mentions/
  reply_to_mode env writes with the existing _skip_env_bridge check, and
  seed auto_thread/reactions/allow_mentions into PlatformConfig.extra (they
  weren't seeded at all before, so a scoped profile's own YAML value would
  otherwise have nowhere to go).
- _reactions_enabled / new _auto_thread_enabled: use the existing
  self._gate_raw() per-profile accessor (the same one _get_allowed_channels
  etc. already use) instead of raw os.getenv, and add both env vars to
  _GATE_ENV_KEYS so they're captured in the connect()-time per-profile
  snapshot.
- _build_allowed_mentions: accept an optional extra param (call site now
  passes self.config.extra), and use _scoped_gate_env (the same helper the
  gate accessors are built on) instead of raw os.getenv for each of the
  four DISCORD_ALLOW_MENTION_* flags, falling back to extra["allow_mentions"]
  when unset. Existing bare _build_allowed_mentions() calls (tests, and any
  future caller with no adapter instance) are unaffected — extra defaults
  to None, preserving the exact prior env-only behavior.

Extends tests/plugins/platforms/test_discord_gate_isolation.py (the
established test file for issue NousResearch#72348's bug class) with 16 new tests
mirroring its existing conventions.
@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins platform/discord Discord bot adapter area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 1, 2026
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

Another consistent application of the #72348 scope-isolation pattern to the Discord adapter: DISCORD_REACTIONS/DISCORD_AUTO_THREAD join the gated env list, _reactions_enabled/_auto_thread_enabled switch to the per-profile _gate_raw accessor (adapter.py:3369-3390), _build_allowed_mentions gains a scope-aware extra.allow_mentions path (adapter.py:525-563), and _apply_yaml_config seeds the same values into seeded_extra while gating the env-bridge writes on _skip_env_bridge (adapter.py:10511-10549) — including the reverse-direction reply_to_mode leak. Test coverage is thorough: isolation between snapshotted adapters, safe defaults, process-env non-leakage, and both bridge directions for every touched key.

Points worth noting:

  • The parse semantics remain asymmetric: _reactions_enabled is deny-list based (not in {"false","0","no"} → any unknown value enables reactions), while _auto_thread_enabled is allow-list based (any unknown value disables auto-threading) (adapter.py:3375-3376, 3385-3386). This mirrors the pre-existing behavior, so it's not a regression, but since both were touched it would be a good moment to decide whether an unknown value should fail safe in both directions.
  • In the scoped path, _scoped_gate_env (i.e. the profile's own .env) takes precedence over extra.allow_mentions (the profile's config.yaml) in _build_allowed_mentions (adapter.py:549-552). Both are the profile's own config, so this is a defensible precedence, but it may be worth documenting that env wins over YAML when both set a scoped profile's mention policy.

Verdict: LGTM

teknium1 added a commit that referenced this pull request Sep 11, 2026
… process env under multiplex

Under gateway.multiplex_profiles every secondary profile's config loads inside
_profile_runtime_scope, yet every apply_yaml_config_fn hook (feishu, matrix,
whatsapp, slack, dingtalk, discord non-gate keys, telegram non-gate keys) and
gateway/config_loader.py::bridge_core_env_settings still wrote os.environ there.
First-writer-wins: the first secondary with a require_mention / allowlist /
allow_bots / reactions block made that policy the DEFAULT profile's (live:
TELEGRAM_REQUIRE_MENTION written from a secondary load), and secondaries read
the default's env for the same keys.

- gateway/platforms/_shared.py::yaml_env_setter: the one env-write shape for
  YAML->env bridges — env wins, skipped under an active secondary scope.
- Every hook now seeds its values into the profile's PlatformConfig.extra and
  uses yaml_env_setter; bridge_core_env_settings seeds telegram/signal
  require_mention into extra and skips the env write under scope.
- Readers that bypassed extra/scope now consult extra first (matrix flags +
  session_scope, slack reactions, telegram reactions/mention_patterns/_extra_bool,
  discord reactions/auto_thread/history_backfill/approval_mentions/allow_mentions,
  feishu allow_bots, dingtalk mention_patterns, signal require_mention).

Salvages the shape of PR #100604 (whatsapp, earliest report #80099), #100435
(discord) and #100448 (telegram) by @nftpoetrist on top of current main.

Fixes #80099

Co-authored-by: nftpoetrist <264138787+nftpoetrist@users.noreply.github.com>
@teknium1

Copy link
Copy Markdown
Collaborator

Superseded by #108440 (on main as 545e74d), with Co-authored-by credit to you. Discord's auto_thread, reactions, allow_mentions.*, history_backfill and approval_mentions bridges now go through the shared yaml_env_setter (skipped under a secondary scope, seeded into extra) and their readers consult extra under scope — the same fix you wrote here, applied uniformly across every adapter in one commit instead of per platform. Thanks, @nftpoetrist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/discord Discord bot adapter sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants