Claude Code Sprachmodus über SSH
Wenn der Server kein Mikrofon hat
بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ
Größe
Abstand
Schrift
- Das fehlende Mikrofon
- Warum es fehlschlägt
- Warum es trotzdem lösbar ist
- Was sonst noch infrage käme
- Das Setup
- Schritt 1: den lokalen Audio-Server für TCP öffnen
- Schritt 2: per SSH mit Reverse-Tunnel einloggen
- Schritt 3: den Remote auf den Audio-Server ausrichten
- Schritt 4: ALSA über PulseAudio leiten
- Schritt 5: testen
- Das Gesamtbild
- Kosten
- Dauerhaft einrichten
- Nebeneffekt: wenn die RemoteForward-Warnung auftaucht
- Szenario 1: CI-Skripte erben die Direktive
- Lösung: Weiterleitung für Skripte deaktivieren
- Szenario 2: eine zweite interaktive Sitzung zum selben Host
- Lösung: SSH-Verbindungs-Multiplexing
- Zusammenfassung
Das fehlende Mikrofon
Der Sprachmodus von Claude Code braucht ein Mikrofon. Auf einem per SSH erreichten Remote-Linux-Server ist keins vorhanden. Schaltet man den Sprachmodus dort ein, wirft der Audio-Stack:
cannot find card '0'Das Mikrofon ist nicht defekt. Server werden üblicherweise ohne Audio-Hardware ausgeliefert. Die Lösung: das lokale Mikrofon per SSH weiterleiten, damit der Remote-Prozess es wie ein eigenes Gerät nutzt.
Warum es fehlschlägt
Der Fehler trägt das Format von ALSA: card '0' ist das Audio-Gerät auf Kernel-Ebene, das die Schicht erwartet. Egal auf welchem Weg der Sprachmodus das Mikrofon auslesen will, er landet bei ALSA. Dort wird nach einem physischen Gerät gesucht, keins gefunden, und der obige Fehler zurückgegeben.
Die Userspace-Daemons, die normalerweise zwischen App und Hardware sitzen, PulseAudio und PipeWire, laufen auf einem typischen Server-Setup ebenfalls nicht. Selbst wenn sie liefen, hätten sie nichts zu verwalten.
Das Problem ist also keine Fehlkonfiguration. Auf der Maschine existiert schlicht keine Audio-Ebene.
Warum es trotzdem lösbar ist
Die Annahme, dass Sprachaufnahme hardware-lokal ist, lässt das Ganze unlösbar wirken. Unter Linux trifft sie nicht zu: Der Daemon ist ein Userspace-Dienst, und die Hardware sitzt dahinter. PipeWire und PulseAudio stellen dasselbe Protokoll über einen Netzwerk-Socket bereit, nicht nur über den lokalen Unix-Socket. Mit der richtigen Konfiguration kann eine App auf einem Rechner das Mikrofon eines anderen aufnehmen.
Das ist der ganze Ansatz: TCP beim lokalen Daemon aktivieren, den Port per SSH weiterleiten, den Remote-Prozess darauf richten.
Was sonst noch infrage käme
Audio über ein Netzwerk hat unter Linux mehrere Varianten. Die Nachbarn dieses Ansatzes:
- Die RTP-Module von PipeWire (
module-rtp-source/module-rtp-sink). Streamen rohes PCM, Opus oder MIDI über UDP. Typischerweise für LAN-Multicast eingesetzt (Standardgruppe224.0.0.56) und nicht für Punkt-zu-Punkt, mit niedrigerem Latenz-Potenzial als TCP, ohne eingebaute Verschlüsselung, und beide Seiten benötigen übereinstimmende Adress- und Formatkonfiguration. module-tunnel-sourcevon PulseAudio. Der Remote lädt dieses Modul und zeigt auf den PulseAudio-Server (oder PipeWires Pulse-Kompatibilitätsschicht) des lokalen Rechners. Dasselbe native Protokoll, kein SSH-Tunnel beteiligt. Das ist zugleich der Preis: keine Verschlüsselung, und der Auth-Cookie reist unverschlüsselt über das Netzwerk.- X11-Forwarding. Wird in Diskussionen zu Audio über SSH oft vorgeschlagen, aber
ssh -Xtransportiert kein Audio. Aufbauten, die Audio an X anflanschen, brauchen trotzdem eine grafische Sitzung und einen separaten Audio-Transport: PulseAudiosmodule-x11-publish(das die Adresse des PA-Servers über X11-Eigenschaften bekannt gibt) oder NX/x2go (die einen eigenen Audio-Kanal neben der X-Sitzung ergänzen). Nichts davon greift auf einem Server ohne grafische Ausgabe. - Claude Code einfach lokal laufen lassen. Immer eine Option. Der Preis: auf die Ressourcen des Remote-Rechners verzichten, auf die Erreichbarkeit von überall, und auf Infrastruktur unter deiner Kontrolle.
Der hier gewählte Weg, TCP über SSH, fügt auf beiden Seiten nichts Neues hinzu (PipeWire ist auf dem Laptop bereits vorhanden, OpenSSH auf beiden), und er ist durch den SSH-Tunnel verschlüsselt.
Das Setup
Drei bewegliche Teile:
- Der lokale Audio-Daemon lauscht auf einem TCP-Port, zusätzlich zum üblichen Unix-Socket.
- SSH trägt diesen Port über einen Reverse-Tunnel zum Remote-Rechner.
- Der Audio-Stack auf der Remote-Seite (sowohl PulseAudio-fähige Apps als auch reine ALSA-Apps) wird auf den Tunnel gerichtet.
Schritt 1: den lokalen Audio-Server für TCP öffnen
Auf dem lokalen Rechner die Standard-PipeWire-PulseAudio-Konfiguration ins eigene Benutzerverzeichnis kopieren:
cp /usr/share/pipewire/pipewire-pulse.conf ~/.config/pipewire/pipewire-pulse.conf~/.config/pipewire/pipewire-pulse.conf öffnen, den server.address-Block suchen und die TCP-Adresse ergänzen:
server.address = [ "unix:native" "tcp:127.0.0.1:4713" # nur localhost, für SSH-Audio-Weiterleitung]PipeWire neu starten, damit die Änderung greift:
systemctl --user restart pipewire pipewire-pulsePipeWire lauscht nun auf TCP-Port 4713, aber nur auf localhost. Aus dem Netzwerk ist er nicht erreichbar. Weil die Änderung in der Konfigurationsdatei steht, überlebt sie Neustarts.
Schritt 2: per SSH mit Reverse-Tunnel einloggen
Statt eines einfachen ssh user@server kommt das -R-Flag hinzu:
ssh -R 4713:127.0.0.1:4713 user@your-serverDas -R-Flag sagt SSH: alles, was sich auf dem Remote-Rechner mit Port 4713 verbindet, wird zu Port 4713 auf dem lokalen Rechner weitergeleitet. Der Audio-Verkehr läuft innerhalb der verschlüsselten SSH-Verbindung.
Schritt 3: den Remote auf den Audio-Server ausrichten
Einmal auf dem Remote eingeloggt, diese Umgebungsvariable setzen:
export PULSE_SERVER="tcp:127.0.0.1:4713"Jede App, die PulseAudio (oder PipeWires Pulse-Kompatibilitätsschicht) nutzt, verbindet sich nun mit diesem Port, und der tunnelt direkt zurück zum lokalen Mikrofon.
Schritt 4: ALSA über PulseAudio leiten
Einige Apps greifen direkt auf ALSA zu, statt über die PulseAudio-Umgebungsvariablen zu gehen. Der Sprachmodus von Claude Code ist eine davon, zu urteilen nach dem ursprünglichen Fehlerformat (cannot find card '0' ist eine ALSA-Meldung). Für solche Apps braucht ALSA eine eigene Routing-Regel. ~/.asoundrc auf dem Remote-Rechner anlegen:
pcm.!default { type pulse }ctl.!default { type pulse }Das sagt ALSA: wenn eine App nach dem Standard-Audiogerät fragt, leite sie über PulseAudio, statt nach Hardware zu suchen. Die benötigten Pakete installieren:
sudo apt install libasound2-plugins pulseaudio-utils alsa-utilsalsa-utils stellt arecord und die aufnahmeseitigen ALSA-Binaries bereit, die der Sprachaufnahme-Pfad braucht. Ohne dieses Paket kann der Audio-Pfad vollständig funktionieren (Tunnel steht, Mikrofon ist mit pactl info sichtbar) und der Sprachmodus scheitert trotzdem, weil kein Aufnahme-Binary existiert, das von der Quelle liest. Eine Lücke, die sich still versteckt: jede Ebene des Stacks testet einzeln sauber, nur das, was tatsächlich Bytes aus dem Mikrofon liest, fehlt. Die Installation des Pakets hat in diesem Setup den Sprachmodus wiederhergestellt; wer in derselben Sackgasse steckt, wird inshallah mit alsa-utils das Loch schließen. Der Closed-Source-Sprachclient ruft wahrscheinlich arecord oder eines seiner Geschwister auf, aber der strikte Befund bleibt: alsa-utils war das fehlende Stück.
Schritt 5: testen
Weiterhin auf dem Remote ausführen:
pactl infoWenn alles funktioniert, zeigt die Ausgabe die Audio-Server-Informationen des lokalen Rechners, einschließlich des lokalen Mikrofons als Standardquelle. Das ist die Bestätigung.
Das Gesamtbild
Von Anfang bis Ende sieht der Audio-Pfad so aus:
%%{init: {"flowchart": {"useMaxWidth": false}} }%%
graph TD
A["Claude Code (Remote)"] --> B["tcp:127.0.0.1:4713 auf dem Remote"]
B --> C["SSH-Reverse-Tunnel"]
C --> D["lokales PipeWire auf Port 4713"]
D --> E["physisches Mikrofon"] Die Stimme geht von Hardware → PipeWire → TCP-Socket → SSH-Tunnel → Remote-Server → Claude Code. Der Remote-Rechner braucht nie eigene Audio-Hardware.
Kosten
Was das kostet:
- Latenz. Der Mikrofon-Stream läuft durch PipeWire, einen TCP-Socket, einen SSH-Verschlüsselungsdurchgang und zurück. Über eine Heim-Verbindung zu einem nahen Server ist das für Diktieren nicht spürbar; auf einer langsamen Leitung oder zu einem weit entfernten Server wird es spürbar sein.
- Bandbreite. Der Stream ist unkomprimiertes PCM. Der Sprachmodus sendet in kurzen Bursts, das Datenvolumen bleibt in der Praxis also klein, eine getetherte oder gedrosselte Leitung merkt es trotzdem.
- Vertrauensumfang. Der Remote-Prozess erhält dein Live-Mikrofon, solange die SSH-Sitzung offen ist. Alles andere, was auf dem Remote-Rechner unter demselben Benutzer läuft, kann währenddessen von derselben Quelle lesen. Das ist normal für jedes weitergeleitete Gerät, aber einen Gedanken wert.
- Annahme eines einzelnen Rechners. Das Setup bindet sich an localhost auf dem Remote, und genau das macht es sicher. Es bedeutet aber auch: nur Prozesse auf diesem einen Remote-Rechner können das Mikrofon nutzen, nicht ein zweiter Rechner, der über denselben Hop erreicht wird.
Dauerhaft einrichten
Das bisherige Setup funktioniert für eine Sitzung. Um es dauerhaft zu machen:
Auf dem Remote. PULSE_SERVER gehört in ~/.zshrc (oder ~/.bashrc):
echo "export PULSE_SERVER='tcp:127.0.0.1:4713'" >> ~/.zshrcFür SSH. RemoteForward in ~/.ssh/config auf dem lokalen Rechner eintragen, damit das -R-Flag nicht jedes Mal nötig ist:
Host your-server HostName your-server-ip User your-user RemoteForward 4713 127.0.0.1:4713Ein einfaches ssh your-server richtet den Tunnel nun automatisch ein.
Für das lokale TCP-Modul. In Schritt 1 bereits erledigt. Port 4713 öffnet sich bei jedem Login.
Nebeneffekt: wenn die RemoteForward-Warnung auftaucht
Sobald RemoteForward 4713 127.0.0.1:4713 in ~/.ssh/config steht, versucht jede SSH-Verbindung zu diesem Host, die Audio-Weiterleitung aufzubauen. Es gibt zwei Situationen, in denen das nicht geht, und beide erzeugen dieselbe Warnung:
Warning: remote port forwarding failed for listen port 4713Die Verbindung kommt trotzdem zustande (das Deploy läuft durch, die Shell öffnet sich normal), aber die Warnung sieht alarmierend aus. Zwei unterschiedliche Szenarien teilen das Symptom.
Szenario 1: CI-Skripte erben die Direktive
Woodpecker CI läuft auf dem lokalen Rechner. Ein leichtgewichtiges, selbst gehostetes CI-System, das Git-Pushes per Webhook entgegennimmt und die Build-Schritte direkt auf dem Host ausführt (keine Container). Der Deploy-Schritt nutzt ssh und rsync, um die gebaute Seite auf einen Remote-Server zu schicken. Beide Befehle verbinden sich mit demselben Host-Alias, der jetzt RemoteForward 4713 in seiner SSH-Konfiguration hat.
Jedes Deploy begann, die Warnung auszugeben. Das Deploy selbst lief sauber: rsync beendete sich, der Symlink wurde umgelegt, die Seite ging live. In einem CI-Log ist diese Art von Warnung eine, die einen innehalten und nachsehen lässt, selbst wenn nichts falsch ist.
Der CI-Prozess läuft in einer Umgebung ohne das Audio-Runtime des lokalen Benutzers: anderer Benutzer, keine interaktive Shell, und der lokale PipeWire-Socket ist von dort, wo der CI-Agent läuft, nicht erreichbar. Die Weiterleitung hat nichts, worauf sie landen kann, also gibt SSH auf und gibt die Warnung aus.
Lösung: Weiterleitung für Skripte deaktivieren
SSH kennt ClearAllForwardings, was es anweist, alle LocalForward- und RemoteForward-Direktiven aus der Konfigurationsdatei zu ignorieren. Pro Befehl setzen mit -o:
ssh -o ClearAllForwardings=yes g12 "mkdir -p /some/path"Für ein Deploy-Skript mit mehreren SSH-Aufrufen einmal oben definieren:
SSH="ssh -o ClearAllForwardings=yes"export RSYNC_RSH="$SSH"RSYNC_RSH sagt rsync, welchen SSH-Befehl es nutzen soll. Jeder $SSH-Aufruf und jeder rsync-Transfer im Skript verbindet sich nun, ohne irgendeine Port-Weiterleitung zu versuchen. Die Warnung verschwindet. Interaktive ssh g12-Sitzungen bekommen weiterhin den Audio-Tunnel, weil sie diesen Override nicht nutzen.
Szenario 2: eine zweite interaktive Sitzung zum selben Host
Der andere Fall zeigt sich, wenn zwei Terminals (oder dasselbe Terminal zu zwei Zeiten) beide ssh g12 aufrufen. Die erste Sitzung baut den RemoteForward auf und bindet Port 4713 auf dem Remote. Die zweite Sitzung liest dieselbe Konfiguration, versucht, 4713 auf dem Remote zu binden, und findet ihn belegt.
Die Warnung erscheint bei der zweiten Sitzung. Der Sprachmodus funktioniert dort trotzdem, überraschenderweise: der Tunnel der ersten Sitzung wird transparent geteilt, weil der gebundene Port zurück zum selben lokalen PipeWire routet. Das Audio fließt. Die Warnung führt in die Irre: sie suggeriert, dass die Weiterleitung für alles fehlgeschlagen ist, obwohl nur der Versuch dieser spezifischen Sitzung, sie ebenfalls zu binden, scheiterte.
Die eigentliche Konsequenz bleibt verborgen: wenn die erste Sitzung jemals geschlossen wird, stirbt der Tunnel für beide. Die zweite Sitzung sieht aus, als wäre sie noch verbunden, aber der Sprachmodus funktioniert nicht mehr, bis eine frische erste Sitzung geöffnet wird.
Lösung: SSH-Verbindungs-Multiplexing
OpenSSH bringt Verbindungs-Multiplexing über ControlMaster bereits mit. Die erste Sitzung öffnet eine Master-Verbindung; spätere Sitzungen zum selben Host teilen sie sich, statt eine neue TCP-Verbindung aufzubauen. Es gibt nur einen RemoteForward, nur einen Tunnel, keine Warnungen bei späteren Sitzungen.
Im Host-Block in ~/.ssh/config ergänzen:
Host your-server HostName your-server-ip User your-user RemoteForward 4713 127.0.0.1:4713 ControlMaster auto ControlPath ~/.ssh/cm_%r@%h:%p ControlPersist 10mControlMaster auto aktiviert das Teilen. ControlPath bestimmt, wo die Socket-Datei der gemultiplexten Verbindung liegt; %r, %h, %p werden zu Benutzer, Host, Port expandiert. ControlPersist 10m hält die Master-Verbindung zehn Minuten nach dem Schließen der letzten Sitzung am Leben, sodass beim Öffnen eines neuen Terminals nicht wieder der SSH-Handshake-Aufwand anfällt.
Damit lassen sich beliebig viele ssh g12-Terminals parallel öffnen: ein einziger Tunnel unter der Haube, keine Warnungen bei späteren Sitzungen, und der Sprachmodus wird aus jedem von ihnen inshallah erreichbar sein. Das Schließen eines Terminals zieht den anderen nicht den Boden unter den Füßen weg. Das ist das Standard-Multiplexing-Muster von OpenSSH, angewendet auf das Audio-Tunnel-Szenario; der zugrundeliegende Mechanismus ist gut dokumentiert, einen Test im eigenen Setup aber wert, bevor man sich darauf verlässt.
Eine saubere Trennung über beide Szenarien hinweg: Skripte steigen aus der Weiterleitung aus, interaktive Sitzungen teilen sich einen Tunnel.
Zusammenfassung
| Was | Wo | Befehl |
|---|---|---|
| TCP-Modul (dauerhaft) | Lokal (~/.config/pipewire/pipewire-pulse.conf) | "tcp:127.0.0.1:4713" zu server.address hinzufügen |
| SSH-Tunnel (dauerhaft) | Lokal (~/.ssh/config) | RemoteForward 4713 127.0.0.1:4713 unter Host your-server |
| Mehrsitzungs-Multiplexing | Lokal (~/.ssh/config) | ControlMaster auto + ControlPath ~/.ssh/cm_%r@%h:%p + ControlPersist 10m |
| Audio-Server setzen | Remote (~/.zshrc) | export PULSE_SERVER='tcp:127.0.0.1:4713' |
| ALSA konfigurieren | Remote (~/.asoundrc) | pcm.!default { type pulse } |
| Pakete installieren | Remote | sudo apt install libasound2-plugins pulseaudio-utils alsa-utils |
| CI-/Skript-Override | Deploy-Skripte | ssh -o ClearAllForwardings=yes + RSYNC_RSH |
Fünf Schritte für das Audio-Setup, ein Block für Multiplexing, ein Override für Skripte, die die Weiterleitung gar nicht tragen sollten. Die Teile sind alle schon an Bord: PipeWire, OpenSSH, ALSA. Die Arbeit liegt darin, sie aufeinander auszurichten.
KI-Editoren 1 von 1
Zurück zu KI-EditorenLuft- und Raumfahrtingenieur
Ethischer Unternehmer in der Öffentlichkeit
Sie kümmern sich um Ihr Geschäft
Ich kümmere mich um die digitale Seite
Zusammenarbeiten
- KI mit Ehrlichkeit
- Private Infrastruktur
- Websites die performen
Erzählen Sie mir von Ihrer Situation:
javed@javedab.com Mehr über mich