Files
management/verfahren/aar/2026-08-11-apo-calls-profile-zeile.md
T
Thore Cimbal ff0cf0d8bd docs: AAR for the @apo call failure - missing Synapse profiles row
Root cause proven end to end: a pre-Authentik account that lost its profiles
row (deactivate clears it, reactivate does not recreate it) crashes the
displayname write path, so it never gets a display name and the Element Call
widget never initialises. Fixed with a cross-checked INSERT; open_id_tokens
went 0 -> 6 and the call joined. Records the ruled-out suspects, what led to
the solution, and the lessons - chief among them: compare old accounts against
freshly provisioned ones, and reproduce in a cleartext room before blaming
crypto. Also notes the account-takeover finding (gitops#61) surfaced along the
way.
2026-08-11 12:00:00 +00:00

6.2 KiB

AAR — @apo konnte nicht telefonieren: fehlende Synapse-profiles-Zeile

Datum: 2026-08-11 · Beteiligt: sorb + Mac-Session · Stack: Synapse, MAS, Authentik, Element Web / Element Call, MatrixRTC (K3s-Cluster) Auftrag: @apo kann sich anmelden und schreiben, aber kein Call kommt zustande — Grundursache finden und beheben, ohne weiter zu raten.

1. Ergebnis

Behoben und verifiziert:

  • @apo telefoniert wieder. Grundursache belegt: dem Konto fehlte die Zeile in Synapses profiles-Tabelle. Fix war ein einzelnes INSERT der Registrierungs-Default-Zeile, an zwei gesunden Konten (clark, calltest01) gegengeprüft.
  • Gegenprobe nach dem Fix: displayname gesetzt (vorher keine Zeile), open_id_tokens 0 → 6, aktives org.matrix.msc3401.call.member im Raum.
  • Dokumentiert: Runbook docs/troubleshooting/CALLS-FEHLEN-PROFILE-ZEILE.md im gitops-Repo (inkl. Index-Eintrag), Merksatz im Session-Gedächtnis.

Nebenbefund, separat behoben:

  • Kontoübernahme-Lücke: Der MAS-Upstream-Provider stand auf claims_imports.localpart.on_conflict: add — bei Localpart-Kollision verknüpfte MAS die neue Upstream-Identität mit einem bestehenden Konto (inkl. Dienstkonten ohne Upstream-Link). Auf on_conflict: fail umgestellt (gitops ef04d86, nach git.lab gepusht), dokumentiert als gitops#61, priority:high. Ausgelöst durch die live reproduzierte case-sensitive Dublette boje/Boje; das Zweitkonto boje (Authentik-ID 11) wurde gelöscht.

2. Die Kausalkette (belegt, nicht vermutet)

Glied Beleg
@apo hat keine profiles-Zeile SELECT count(*) … = 0, während clark/sorb/calltest01 je eine haben
Displayname-Setzen crasht PUT …/displayname → 500, TypeError: 'NoneType' object is not subscriptable in _check_profile_size (storage/databases/main/profile.py:354) — txn.fetchone() liefert None, row[0] fliegt
kein Displayname → Widget-Init bricht ab Call-Klick erzeugte null Server-Aktivität: kein openid/request_token, kein call.member; Browser-Log damals „Messaging present but not yet started" (iframe meldet nie ContentLoaded)
kein Widget → kein Token → keine SFU @apo als einziger aktiver Nutzer mit 0 Einträgen in open_id_tokens (die nicht geprunt werden)

Herkunft der fehlenden Zeile: @apo ist ein Vor-Authentik-Konto, das durch sechs Identitäts-Resets ging. Deaktivieren löscht in Synapse das Profil, Reaktivieren legt es nicht neu an. frank (noch älter, nie zurückgesetzt) behielt seine Zeile. Ob einer der früheren manuellen Eingriffe der auslösende Reset war, ist nicht mehr zweifelsfrei zu klären — die Zeile ist jetzt wieder da.

3. Was ausgeschlossen wurde (gemessen)

Verdacht Warum entkräftet
Krypto / Cross-Signing (18 Pseudo-Geräte aus 6 Resets) Testraum ist unverschlüsselt → Call braucht keine Krypto; clark telefoniert mit ebenfalls zurückgesetzten Schlüsseln
Server-Call-Pfad (SFU, RTC-Auth, OpenID-Endpoint) calltest01/sorb bekommen sauber 200 auf openid/request_token und die Federation-Auflösung
@apos Token / Session /sync läuft durchgehend mit 200, Messaging intakt
Login-Verknüpfung MAS↔Authentik subject = Authentik-uid 2fafe38b…, korrekt

4. Was zur Lösung geführt hat

  • Der Sprung von „welcher Nutzer telefoniert nicht" zu „welche Tabelle ist anders". Der Durchbruch war die open_id_tokens-Abfrage über alle aktiven Nutzer: @apo = 0, alle anderen zweistellig+. Ein Vergleich statt einer Einzelbetrachtung.
  • Ein unverschlüsselter Testraum hat das größte Ablenkungsfeld (Cross-Signing) in einem Schritt geschlossen.
  • Der Live-Mitschnitt beim echten Call-Klick zeigte die Abwesenheit jeder Aktivität — nicht ein Fehler, sondern nichts war der Befund.
  • Der Nutzer-Hinweis „Anzeigename konnte nicht gesetzt werden" lieferte den 500er mit vollständigem Stacktrace — die letzte Meile von Korrelation zu Ursache.
  • Der entscheidende Kontext kam von sorb:apo ist ein Alt-Konto von vor der Authentik-Integration." Das lenkte die Suche von „angesammelter Müll" auf „Migrations-/Provisionierungs-Lücke".

5. Lehren für die Zukunft

  1. Bei Call-Problemen zuerst open_id_tokens je Nutzer vergleichen. 0 bei einem sonst aktiven Konto ist das schnellste, eindeutigste Alarmsignal und trennt Client- von Server-Ursache in einer Abfrage.
  2. Immer im unverschlüsselten Raum reproduzieren, bevor man Krypto verdächtigt. Das schließt einen ganzen Ursachenblock kostenlos aus.
  3. „Nichts passiert" ist ein Messergebnis, kein Sackgassen-Signal. Die Abwesenheit eines openid-Aufrufs hat den Fehler lokalisiert, nicht ein Fehlercode.
  4. Alt-/mehrfach-zurückgesetzte Konten gegen frisch provisionierte diffen, nicht nur gegen die Erwartung. Der Unterschied war eine fehlende Zeile — sichtbar nur im direkten Vergleich mit clark/calltest01.
  5. Jeder DB-Schreib strukturiert: betroffene Zeile vorher anzeigen, an einem gesunden Konto gegenprüfen, per INSERT … ON CONFLICT DO NOTHING statt Überschreiben. Das ist die direkte Konsequenz aus den früheren unstrukturierten MAS-Eingriffen dieses Vorgangs — und diesmal eingehalten.
  6. Beiläufige Symptome ernst nehmen: die Dublette boje/Boje beim Testkonto-Anlegen war der Faden, der die Kontoübernahme-Lücke (gitops#61) aufdeckte — ein Sicherheitsfund, der ohne den @apo-Vorgang unentdeckt geblieben wäre.

6. Offen / Folgetodos

  • gitops#61 (on_conflict-Härtung) ist gepusht und rollt über Flux; der case-insensitive Eindeutigkeits-Check im matrix-invitation-Prompt-Stage (damit der Nutzer schon bei der Registrierung statt erst beim Login scheitert) ist dort als bewusst offener Rest vermerkt.
  • Verwaiste Altlasten bei @apo (10 local_notification_settings für längst gelöschte Geräte, 18 Cross-Signing-Pseudoeinträge) sind kosmetisch und wurden bewusst nicht angefasst — sie haben mit dem Call-Problem nichts zu tun, und ein weiterer Eingriff widerspräche der Lehre oben.