Skip to content

Matrix: Voice messages sent as audio files instead of native voice messages #14225

@tfm-neo-ai

Description

@tfm-neo-ai

Bug Description

When TTS is enabled or when sending voice messages via the message tool with asVoice: true, the messages are delivered as audio file attachments instead of native voice message bubbles in Matrix clients (Element, FluffyChat, etc.).

Expected Behavior

Voice messages should display as native voice message bubbles (with waveform, duration, play button) in Matrix clients.

According to MSC3245, this requires "org.matrix.msc3245.voice": {} in the content object.

Actual Behavior

Audio is sent as m.audio attachment without the voice indicator, displaying as a generic file card.

Root Cause Found

In extensions/matrix/src/outbound.ts, the sendMedia adapter is missing the audioAsVoice parameter:

sendMedia: async ({ to, text, mediaUrl, deps, replyToId, threadId }) => {
  const send = deps?.sendMatrix ?? sendMessageMatrix;
  const resolvedThreadId =
    threadId !== undefined && threadId !== null ? String(threadId) : undefined;
  const result = await send(to, text, {
    mediaUrl,
    replyToId: replyToId ?? undefined,
    threadId: resolvedThreadId,
    // ❌ MISSING: audioAsVoice is not passed here
  });
  ...
}

The underlying sendMessageMatrix function in send.ts supports audioAsVoice: true (line ~70-75), and buildMediaContent in media.ts correctly sets org.matrix.msc3245.voice: {} when isVoice: true, but the sendMedia adapter never passes this flag.

The message tool exposes asVoice: true, but this is not being propagated to audioAsVoice.

Affected Components

  • extensions/matrix/src/outbound.ts - sendMedia adapter
  • extensions/matrix/src/matrix/send.ts - sendMessageMatrix function (already supports the parameter)

Environment

  • OpenClaw version: 2026.2.9
  • TTS provider: ElevenLabs (eleven_multilingual_v2)
  • Channel: Matrix
  • Client: Element Web/Desktop

Related Issues

Suggested Fix

Update sendMedia in outbound.ts to accept and pass audioAsVoice: true when asVoice is set in the message tool options.

sendMedia: async ({ to, text, mediaUrl, deps, replyToId, threadId, asVoice }) => {
  ...
  const result = await send(to, text, {
    mediaUrl,
    replyToId: replyToId ?? undefined,
    threadId: resolvedThreadId,
    audioAsVoice: asVoice, // ✅ Add this line
  });
  ...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstaleMarked as stale due to inactivity

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions