Skip to content

fix(gateway): accept is_voice kwarg in Matrix, Mattermost, LINE send_voice overrides - #113952

Open
seth586 wants to merge 2 commits into
NousResearch:mainfrom
seth586:fix/media-send-voice-kwarg
Open

seth586 wants to merge 2 commits into
NousResearch:mainfrom
seth586:fix/media-send-voice-kwarg

Conversation

@seth586

@seth586 seth586 commented Sep 17, 2026

Copy link
Copy Markdown

Summary

MEDIA: audio replies (TTS voice messages, audio deliverables) are silently dropped on Matrix, Mattermost, and LINE: the text arrives, but no voice bubble or file attachment is ever uploaded. Fixes #113951.

Root cause

The shared media-delivery chain passes is_voice= to every adapter's send_voice():

Call site Handling
gateway/platforms/base.py _send_one (~L3953) direct call — error logged per attempt
gateway/run_notifications.py (~L326) caught by broad except — warning logged
gateway/run_turn.py (~L2406) wrapped in suppress(Exception)fully silent

BasePlatformAdapter.send_voice accepts **kwargs, but three plugin adapters override it with a narrower signature and no kwarg tolerance, so the call raises TypeError: send_voice() got an unexpected keyword argument 'is_voice' before any upload happens:

  • plugins/platforms/matrix/adapter.py
  • plugins/platforms/mattermost/adapter.py
  • plugins/platforms/line/adapter.py

Observed on Matrix (gateway.log):

WARNING gateway.platforms.base: [Matrix] Error sending media: MatrixAdapter.send_voice() got an unexpected keyword argument 'is_voice'

Fix

Accept-and-ignore **kwargs in the three send_voice overrides, matching the base adapter's signature tolerance. Voice-vs-file routing is decided by the caller (should_send_media_as_audio + the [[audio_as_voice]] media tag); the adapters don't need the flag, they just must not reject it.

Testing

  • New invariant test tests/gateway/test_media_send_voice_kwargs.py: the three overrides must tolerate the dispatcher's exact call shape (is_voice= + metadata=), and the base signature binds the dispatcher's kwargs. Red on base (3 failed), green with this fix.
  • Existing voice/media suites pass: test_matrix_voice.py, test_voice_transcode.py, test_tts_media_routing.py (16 passed, 1 skipped).
  • Verified live on Matrix: before the fix, TTS replies delivered text only with the log warning above; after the fix, TTS replies arrive as native voice/audio messages with no warnings.

Notes for reviewers

  • The is_voice flag already reaches adapters through other signatures (e.g. Matrix passes it internally to _send_local_file(..., is_voice=True) for the MSC3245 voice field); this PR only removes the signature mismatch at the override boundary.
  • Same bug class was swept across all plugins/platforms/*/adapter.py overrides of send_voice/send_document/send_image_file/send_video; Matrix, Mattermost, and LINE were the only three affected.

…voice overrides

The shared media dispatcher (gateway/platforms/base.py _send_one,
gateway/run_notifications.py, gateway/run_turn.py) passes
is_voice= to adapter.send_voice() for every non-image audio
attachment. Three plugin adapters overrode send_voice with a
narrower signature and no **kwargs, so the call raised TypeError
before any upload happened and MEDIA audio replies were dropped
silently (run_turn.py wraps its call site in suppress(Exception)).

Affected adapters:
- Matrix (no voice bubbles or audio attachments delivered)
- Mattermost
- LINE

Fix: accept-and-ignore **kwargs in the three overrides, matching
the base adapter's signature tolerance (routing is decided by the
caller). Add an invariant test asserting the overrides tolerate
the dispatcher's exact call shape (red on base, green with fix).

Verified live on Matrix: TTS voice replies deliver as native audio
after the fix.
@KeyArgo

KeyArgo commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Independent reproduction on current main (bbaf7af5c83), binding the exact kwargs the shared dispatcher passes (gateway/platforms/base.py:3953):

inspect.signature(Adapter.send_voice).bind(chat_id, audio_path, is_voice=True)

MatrixAdapter:      TypeError: got an unexpected keyword argument 'is_voice'
MattermostAdapter:  TypeError: got an unexpected keyword argument 'is_voice'
LineAdapter:        TypeError: got an unexpected keyword argument 'is_voice'
WeixinAdapter:      TypeError: got an unexpected keyword argument 'is_voice'

Two boundary notes that may help the maintainer pick the carrier:

The test here asserts the right invariant: the dispatcher contract (an adapter's override must tolerate the kwargs the shared media path passes), not the individual signatures — that is what broke, and what the silent suppress(Exception) in gateway/run_turn.py turned into data loss rather than a visible error.

Same bug class as the Matrix/Mattermost/LINE send_voice overrides:
WeixinAdapter.send_video and send_voice define narrow signatures
without **kwargs, so the shared media dispatcher's is_voice= kwarg
raises TypeError before upload and audio/video MEDIA attachments
are silently dropped (gateway/platforms/weixin.py).

Adds **kwargs to both overrides, matching the base adapter's
signature tolerance.
@seth586

seth586 commented Sep 17, 2026

Copy link
Copy Markdown
Author

Two updates:

  1. Prior reports. This was independently reported in [Bug]: Gateway media router: audio MEDIA: attachments silently fail on Mattermost and LINE (send_voice rejects is_voice kwarg) #100018 (Mattermost + LINE), Matrix voice delivery broken in v0.21.0: send_voice() got unexpected kwarg 'is_voice' #102221 (Matrix), and [Bug]: Weixin audio MEDIA: attachments silently dropped — send_voice rejects the router's is_voice kwarg #101380/[Bug] Weixin: send_voice()/send_video() drop **kwargs, audio attachments silently fail #113640 (Weixin) — all still open. This PR consolidates the fix for the whole class.

  2. Weixin added (d599158): WeixinAdapter.send_voice/send_video have the same narrow-signature issue, so this PR now covers all four affected adapters. Invariant test extended coverage passes; the one failing test_weixin.py case (QR-login clock) fails on base as well and is unrelated.

@seth586

seth586 commented Sep 17, 2026

Copy link
Copy Markdown
Author

Thanks @KeyArgo — the census and dispatcher-contract framing are exactly right. One timing note: the Weixin override was added to this PR in d599158 just before your comment landed, so all four are covered here and no exclusion needs stating. @101381 and #113645 also carry the Weixin fix — happy to defer to whichever carrier the maintainers prefer; if one of those merges first, the Weixin commit here can be dropped without affecting the other three adapters.

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/matrix Matrix adapter (E2EE) platform/wecom WeCom / WeChat Work adapter P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Sep 17, 2026
@KeyArgo

KeyArgo commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Independent verification of this patch on current main (98f758a), driving the real dispatch path rather than only the signature.

Method: applied this diff to a clean checkout, then called BasePlatformAdapter._deliver_media_attachments (the production call site, gateway/platforms/base.py _send_one at ~L3948-3979) with a (path, is_voice=True) MEDIA entry and only each adapter's private upload helper stubbed, so the override body itself runs.

Without the patch — all four adapters, and the warning matches the report verbatim:

[Weixin] Error sending media: WeixinAdapter.send_voice() got an unexpected keyword argument 'is_voice'
[Line] Error sending media: LineAdapter.send_voice() got an unexpected keyword argument 'is_voice'
[Matrix] Error sending media: MatrixAdapter.send_voice() got an unexpected keyword argument 'is_voice'
[Mattermost] Error sending media: MattermostAdapter.send_voice() got an unexpected keyword argument 'is_voice'
  dispatch result: success=False error="...got an unexpected keyword argument 'is_voice'"  -> SILENTLY DROPPED

With the patch applied — 4/4 success=True, error=None (DELIVERED), and the base-class signature control still binds.

The new test file reproduces its claimed RED/GREEN as well: 3 failed, 1 passed on base, 4 passed with the patch — and the base-class control (test_dispatch_send_voice_with_is_voice_reaches_base_sender) passes on both, so the RED is the override defect, not a test artifact.

Two review notes, both small:

  1. Coverage gap for the fourth adapter this PR touches. The diff also relaxes WeixinAdapter.send_voice/send_video, but _VOICE_OVERRIDES lists only LINE/Matrix/Mattermost. Adding ("gateway.platforms.weixin", "WeixinAdapter") keeps the regression guard in sync with the change set — weixin is currently the only adapter changed here with no test row.

  2. Diagnostic shape of the failing assertion. On base the parametrized case dies as AttributeError: 'NoneType' object has no attribute 'kind' (line 34) rather than the intended contract message, because sig.parameters.get("kwargs") is None when **kwargs is absent. assert "kwargs" in sig.parameters and sig.parameters["kwargs"].kind is inspect.Parameter.VAR_KEYWORD fails with the message you wrote instead. Not blocking — the RED is real either way.

For the queue: this class also has open carries #107289, #104248, #100679 and #99712 (Matrix-only), and the fresh report #114134 — the Matrix [MEDIA:] symptom reported again today. This PR is the broadest of them (Matrix + Mattermost + LINE + weixin in one change with a test), so it looks like the natural carrier for the reporter-facing duplicate to be folded into.

@kvnloo kvnloo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is correct. The shared media dispatcher (gateway/platforms/base.py:4058) calls self.send_voice(..., is_voice=is_voice), and these four overrides narrowed the base signature without **kwargs — so every TTS voice reply on Matrix, LINE, Mattermost, and Weixin raised TypeError before upload, silently dropped since the call site wraps in suppress(Exception). The four **kwargs additions restore the base contract exactly; the Matrix docstring addition documents the accept-and-ignore, matching the base adapter's own tolerance.

One gap: the new contract test doesn't cover Weixin. _VOICE_OVERRIDES lists only line/matrix/mattermost, but this PR also fixes WeixinAdapter.send_voice (and send_video). Suggest adding ("gateway.platforms.weixin", "WeixinAdapter") to the list so the fourth adapter gets the same regression guard.

Minor: the Weixin send_video hunk is defensive — the current dispatch (base.py:4062) doesn't pass extra kwargs to send_video, so nothing breaks either way. Fine to keep for contract symmetry.

Verdict: correct fix, one test-coverage nit above. Safe to merge once the Weixin entry is added (or as-is; the runtime fix itself is complete).

This branch has not been deployed

No deployments
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 P3 Low — cosmetic, nice to have platform/matrix Matrix adapter (E2EE) platform/wecom WeCom / WeChat Work adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Matrix/Mattermost/LINE: MEDIA audio replies silently dropped — send_voice() TypeError on is_voice kwarg

4 participants