Skip to content

fix(discord): a role-authorized member's speech, native slash commands and /thread starters reach the agent - #133478

Merged
unsupportedpastels merged 6 commits into
NousResearch:mainfrom
unsupportedpastels:fix/discord-role-grant-native-voice
Oct 6, 2026
Merged

unsupportedpastels merged 6 commits into
NousResearch:mainfrom
unsupportedpastels:fix/discord-role-grant-native-voice

Conversation

@unsupportedpastels

@unsupportedpastels unsupportedpastels commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

A member admitted only by DISCORD_ALLOWED_ROLES passes the Discord adapter's gates but was refused by the gateway on three paths, because the event they produced did not carry role_authorized:

The grant has the message path's meaning (_discord_message_admission): a role allowlist exists and this actor passes the gate. It is evaluated for each actor, never inherited:

  • Slash and /thread events call _slash_role_grant(interaction), which re-runs _evaluate_slash_authorization for that interaction instead of relying on call order.
  • A voice turn recomputes the grant for the current speaker against the guild's current member, never the joiner's. A speaker without the role, or one no longer in the guild, stays refused.

Related Issue

Fixes #130302
Fixes #118958

Salvages #130303 (@jonpol01), which includes @Christopher-Schulze's commits from #125620; all five commits are cherry-picked with authorship kept. #118966 (@KoNit-K) fixes the same slash-role problem and is superseded by this PR. Thanks to @andrexibiza for the review.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • plugins/platforms/discord/adapter.py: _build_slash_event and _dispatch_thread_session pass role_authorized=self._slash_role_grant(interaction).
  • plugins/platforms/discord/adapter_slash_auth.py (new): _evaluate_slash_authorization moves here unchanged as DiscordSlashAuthMixin, next to the new _slash_role_grant that reuses it. The move keeps adapter.py within its code-health file cap (7,305 lines, down from 7,368); review with git diff --color-moved.
  • gateway/run_voice.py: _handle_voice_channel_input recomputes role_authorized for the speaker when the adapter has a role allowlist.
  • tests/gateway/test_discord_slash_auth.py: a slash command and a /thread starter from a role member pass the real gateway check; an actor with an unrelated role does not.
  • tests/gateway/test_discord_voice_role_grant.py (new): a role member's speech is dispatched; a speaker without the role (while the joiner has it) and a speaker who left the guild are not.
  • website/docs/user-guide/features/voice-mode.md and its zh-Hans copy: voice access follows DISCORD_ALLOWED_ROLES too.

Conflict resolution against main: #133463 changed _dispatch_thread_session to take the created thread. main's body is kept and only the role_authorized line is added; the new thread-starter test passes a thread object.

How to Test

  1. scripts/run_tests.sh tests/gateway/test_discord_slash_auth.py tests/gateway/test_discord_voice_role_grant.py gives 22 passed. With main's adapter.py and run_voice.py, the 3 role-member cases fail; the refusal cases pass on both.
  2. 83 Discord, voice, auth, prompt-freeze and title-lane test files give 609 passed, 0 failed, 2 skipped.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q and all tests pass. Not run in full; the related suites pass.
  • python scripts/check passes (11 checks)
  • I've added tests for my changes
  • I've tested on my platform: Linux

Documentation & Housekeeping

  • Documentation: updated voice-mode.md (en, zh-Hans)
  • cli-config.yaml.example: N/A
  • CONTRIBUTING.md / AGENTS.md: N/A
  • Cross-platform impact: N/A
  • Tool descriptions/schemas: N/A

Infographic

discord-role-grant

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/discord Discord bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Oct 5, 2026
jonpol01 and others added 6 commits October 6, 2026 00:10
…aches the agent

With DISCORD_ALLOWED_ROLES and no user allowlist, the voice listen loop
admits a role member through the adapter's guild-scoped role check, but
_handle_voice_channel_input rebuilds the turn's source from the
`/voice join` copy. SessionSource.to_dict() deliberately drops the
per-event role_authorized grant, so the gateway's own check saw a
principal with no grant and dropped every spoken turn from a role-only
member, while the same member's typed messages were answered.

The grant is now recomputed for the current speaker against the guild's
current member, with the meaning the message path gives it (the adapter
has a role allowlist and this user passes its gate). It is never
serialized and never inherited from whoever ran `/voice join`: a speaker
without the role, or no longer in the guild, is still refused.

Reported by andrexibiza (F4, voice half) on NousResearch#128788.

(cherry picked from commit 6985ea6)
Review feedback: the comment read as if the stamped role_authorized
value were a per-user role match. It is the same adapter-level verdict
the message path stamps: a role allowlist exists and the user passed
the slash authorization gate.

(cherry picked from commit e4165fc)
… for its own actor only

The salvaged NousResearch#125620 commits stamp role_authorized on _build_slash_event
and _dispatch_thread_session from bool(_allowed_role_ids), which is only
right because today's two callers run _check_slash_authorization first.
Neither builder checks its actor itself, so any later caller that builds
an event before (or without) the gate would mint a grant for a member the
gate refuses.

The grant is now re-evaluated from the interaction in both builders
(_slash_role_grant): a role allowlist exists AND this actor passes the
slash gate, the meaning _discord_message_admission gives the flag. A
role-only member still reaches the agent, and an unrelated-role actor's
event is refused by the gateway even when built directly. The two
salvaged tests fold into one parametrized invariant over both builders
with the refusal row.

Recomputing the verdict from the actor follows NousResearch#118966 by KoNit-K.
Reported by andrexibiza (F4, native half) on NousResearch#128788 and NousResearch#130303.

Co-authored-by: KoNit-K <konit.block@protonmail.com>
(cherry picked from commit 357e77d)
…apt the thread-starter test

_evaluate_slash_authorization moves unchanged into adapter_slash_auth.py beside the new _slash_role_grant, so adapter.py stays within its code-health file cap. The thread-starter test passes a thread, as _dispatch_thread_session now takes one, and the voice role-grant test gains type hints.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P2 Medium — degraded but workaround exists platform/discord Discord bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

4 participants