element-hq / element-hq/element-call

Media undecryptable (one-way video) in self-hosted MatrixRTC+LiveKit — "No targets found for sending key"

Open
#4,127 3 comments 5 reactions 0 assignees View on GitHub
T-Defect
Dominant language
TypeScript
Stars
996
Forks
213
Avg merge
1d 6h
Merged PRs (30d)
54

Description

### Steps to reproduce

1. Where are you starting? What can you see?
2. What do you click?
3. More steps…
# Media stays undecryptable (one-way video/audio) in self-hosted MatrixRTC + LiveKit calls — "[ToDeviceKeyTransport] No targets found for sending key"

## Summary

In 1:1 calls between two Element X (Android) clients against a self-hosted Homeserver (Synapse) + self-hosted LiveKit SFU (MatrixRTC backend, no Element Call SaaS), one participant's media is frequently undecryptable on the other side (video freezes / stays blank one-way, occasionally shown as "reconnecting"), while the underlying WebRTC transport (ICE/DTLS/SFU connection) is completely healthy — confirmed via LiveKit server logs showing a clean session with no ICE failures, no relay fallback needed, and no transport-level disconnects.

App logs from the affected device show the encryption key for the call repeatedly failing to be distributed:

```
[ElementCall] :2 [ToDeviceKeyTransport] No targets found for sending key
```

This line appears at (or shortly after) the start of essentially every call attempt in our logs, regardless of whether the call ultimately "works" for a while or not.

## Environment

- **Client:** Element X for Android, version **26.07.1 (202607010)**, build `i8PXeVBEEC`, device: Samsung SM-S921B, Android 16
- **Client SDK (from LiveKit participant info):** sdk "JS", version "2.19.2", protocol 17
- **Homeserver:** self-hosted Synapse, no Docker
- **MatrixRTC backend:** self-hosted LiveKit SFU (`livekit-server` 1.13.4) + `lk-jwt-service`, fronted by nginx (reverse proxy for `/livekit/jwt/` and `/livekit/sfu`)
- **Call type:** 1:1 DM call, `perParticipantE2EE=true` (visible in the Element Call widget URL), `intent=start_call_dm`
- **Network:** reproduced both on client Wi-Fi and on client mobile data; also reproduced with client routed through a VPN terminating in the same datacenter/AS as the homeserver (symptom persisted less often but was not fully eliminated)

## Steps to reproduce

1. Two Matrix accounts on the same self-hosted homeserver, both on Element X Android, in a DM room.
2. User A starts a video call (`m.call` widget / Element Call embedded widget).
3. User B joins.
4. Within the first ~15-60 seconds, one side's video (sometimes audio) becomes undecryptable / stays blank on the other participant's screen. UI sometimes shows "reconnecting" even though no reconnect is actually happening at the transport layer.

## Evidence that this is a key-distribution issue, not a network/transport issue

- LiveKit server logs (`journalctl -u livekit`) for the same call window show:
- ICE connected directly (host ↔ prflx), no TURN relay needed
- No `short ice connection`, no `TRANSPORT_FAILURE`, no unexpected disconnects
- Session ends cleanly via `CLIENT_REQUEST_LEAVE` after ~50s, as expected
- Client-side app log (`logs.-.log`, rust tracing output) for the *same* call window contains:
```
WARN elementx: [ElementCall] :2 [ToDeviceKeyTransport] No targets found for sending key
```
repeated across multiple call attempts (different timestamps, different call sessions), always near call start.
- Independently, network path was checked (traceroute + 74-packet ICMP ping test): 0% packet loss, average RTT ~40-48ms, occasional jitter spikes to ~85-98ms. No sustained packet loss or route anomaly was found, so we don't believe the underlying network is the root cause — at most it may widen an existing race window.

This matches the previously reported #2415 ("Sender key distribution unreliable when running in embedded mode") — same embedded-widget-in-Element-X scenario, same symptom (recipient can't decrypt media, one-way blank video), described there as intermittent. We're able to reproduce it fairly reliably in a self-hosted (non-SaaS) MatrixRTC/LiveKit setup and have the "No targets found for sending key" log line to go with it, which wasn't mentioned in #2415.

## Question for maintainers

- Is `[ToDeviceKeyTransport] No targets found for sending key` expected to be logged transiently (e.g. before the other participant's call-membership state event has propagated) and then retried, or is a single failure here terminal for that key/session (i.e. no retry happens once the target *is* known)?
- Any known interaction between key distribution timing and `/sync` (sliding sync) latency/jitter on the client? Our client-side sync round-trip appeared to be in the 30-95ms range with visible jitter, which we suspect may be widening whatever race is causing "no targets found" to fire.

## Attachments available on request

- Full `journalctl -u livekit` output for a reproduced call window
- Full Element X app log excerpt around a reproduced call window (rageshake export)
- ping/traceroute results from the affected client

---
*(Before submitting: attach the relevant log excerpts/files as files rather than pasted inline, since GitHub issues have size limits and raw logs may contain tokens/PII that should be redacted first — see note below.)*

## ⚠️ Before submitting — redact these from any attached logs

- Matrix access tokens (`Authorization: Bearer ...`)
- `ice-pwd` / `ice-ufrag` values in SDP blobs (session-scoped, low risk, but redact anyway)
- Room IDs / user IDs if you'd prefer not to disclose your server's identifiers publicly
- Your server's public IP (`155.212.245.145` was used as an example above — replace with a placeholder if submitting publicly)

### Outcome

#### What did you expect?

#### What happened instead?
# Media stays undecryptable (one-way video/audio) in self-hosted MatrixRTC + LiveKit calls — "[ToDeviceKeyTransport] No targets found for sending key"

## Summary

In 1:1 calls between two Element X (Android) clients against a self-hosted Homeserver (Synapse) + self-hosted LiveKit SFU (MatrixRTC backend, no Element Call SaaS), one participant's media is frequently undecryptable on the other side (video freezes / stays blank one-way, occasionally shown as "reconnecting"), while the underlying WebRTC transport (ICE/DTLS/SFU connection) is completely healthy — confirmed via LiveKit server logs showing a clean session with no ICE failures, no relay fallback needed, and no transport-level disconnects.

App logs from the affected device show the encryption key for the call repeatedly failing to be distributed:

```
[ElementCall] :2 [ToDeviceKeyTransport] No targets found for sending key
```

This line appears at (or shortly after) the start of essentially every call attempt in our logs, regardless of whether the call ultimately "works" for a while or not.

## Environment

- **Client:** Element X for Android, version **26.07.1 (202607010)**, build `i8PXeVBEEC`, device: Samsung SM-S921B, Android 16
- **Client SDK (from LiveKit participant info):** sdk "JS", version "2.19.2", protocol 17
- **Homeserver:** self-hosted Synapse, no Docker
- **MatrixRTC backend:** self-hosted LiveKit SFU (`livekit-server` 1.13.4) + `lk-jwt-service`, fronted by nginx (reverse proxy for `/livekit/jwt/` and `/livekit/sfu`)
- **Call type:** 1:1 DM call, `perParticipantE2EE=true` (visible in the Element Call widget URL), `intent=start_call_dm`
- **Network:** reproduced both on client Wi-Fi and on client mobile data; also reproduced with client routed through a VPN terminating in the same datacenter/AS as the homeserver (symptom persisted less often but was not fully eliminated)

## Steps to reproduce

1. Two Matrix accounts on the same self-hosted homeserver, both on Element X Android, in a DM room.
2. User A starts a video call (`m.call` widget / Element Call embedded widget).
3. User B joins.
4. Within the first ~15-60 seconds, one side's video (sometimes audio) becomes undecryptable / stays blank on the other participant's screen. UI sometimes shows "reconnecting" even though no reconnect is actually happening at the transport layer.

## Evidence that this is a key-distribution issue, not a network/transport issue

- LiveKit server logs (`journalctl -u livekit`) for the same call window show:
- ICE connected directly (host ↔ prflx), no TURN relay needed
- No `short ice connection`, no `TRANSPORT_FAILURE`, no unexpected disconnects
- Session ends cleanly via `CLIENT_REQUEST_LEAVE` after ~50s, as expected
- Client-side app log (`logs.-.log`, rust tracing output) for the *same* call window contains:
```
WARN elementx: [ElementCall] :2 [ToDeviceKeyTransport] No targets found for sending key
```
repeated across multiple call attempts (different timestamps, different call sessions), always near call start.
- Independently, network path was checked (traceroute + 74-packet ICMP ping test): 0% packet loss, average RTT ~40-48ms, occasional jitter spikes to ~85-98ms. No sustained packet loss or route anomaly was found, so we don't believe the underlying network is the root cause — at most it may widen an existing race window.

This matches the previously reported #2415 ("Sender key distribution unreliable when running in embedded mode") — same embedded-widget-in-Element-X scenario, same symptom (recipient can't decrypt media, one-way blank video), described there as intermittent. We're able to reproduce it fairly reliably in a self-hosted (non-SaaS) MatrixRTC/LiveKit setup and have the "No targets found for sending key" log line to go with it, which wasn't mentioned in #2415.

## Question for maintainers

- Is `[ToDeviceKeyTransport] No targets found for sending key` expected to be logged transiently (e.g. before the other participant's call-membership state event has propagated) and then retried, or is a single failure here terminal for that key/session (i.e. no retry happens once the target *is* known)?
- Any known interaction between key distribution timing and `/sync` (sliding sync) latency/jitter on the client? Our client-side sync round-trip appeared to be in the 30-95ms range with visible jitter, which we suspect may be widening whatever race is causing "no targets found" to fire.

## Attachments available on request

- Full `journalctl -u livekit` output for a reproduced call window
- Full Element X app log excerpt around a reproduced call window (rageshake export)
- ping/traceroute results from the affected client

---
*(Before submitting: attach the relevant log excerpts/files as files rather than pasted inline, since GitHub issues have size limits and raw logs may contain tokens/PII that should be redacted first — see note below.)*

## ⚠️ Before submitting — redact these from any attached logs

- Matrix access tokens (`Authorization: Bearer ...`)
- `ice-pwd` / `ice-ufrag` values in SDP blobs (session-scoped, low risk, but redact anyway)
- Room IDs / user IDs if you'd prefer not to disclose your server's identifiers publicly
- Your server's public IP (`155.212.245.145` was used as an example above — replace with a placeholder if submitting publicly)

### Operating system

_No response_

### Browser information

_No response_

### URL for webapp

_No response_

### Will you send logs?

Yes

Contributor guide

Open the contributing guide

Research direction

Start with the ToDeviceKeyTransport log path and the Element Call embedded widget's 1:1 call setup, then compare key distribution with /sync timing. Reproduce the call using the stated two-Android-client setup while collecting the Element X and LiveKit logs. Done means media remains decryptable and the warning no longer leaves one-way audio or video.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, typescript
Domain
audio-video-rtc, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.