7 Min. Lesezeit

Unbekannten entfernten Status nach einer SSH-Trennung prüfen

Bewältige einen unbekannten Status auf dem entfernten Host nach einer SSH-Trennung mit einer dauerhaften Lauf-ID, einem entfernten Statusdatensatz, Nachbedingungsprüfungen und sicheren Entscheidungen über Wiederholungen.

Unbekannten entfernten Status nach einer SSH-Trennung prüfen

Eine getrennte SSH-Verbindung bedeutet nicht, dass der entfernte Befehl fehlgeschlagen ist. Sie bedeutet, dass der Client die Belege verloren hat, die für eine Einordnung nötig wären. Das ist ärgerlich, wenn du uname ausgeführt hast, und teuer, wenn es um eine Datenbankmigration, ein Release, eine Zugangsdatenrotation oder einen Befehl ging, der eine externe API aufruft.

Die Lösung ist kein längeres Timeout. Du brauchst eine entfernte Prüfsequenz mit einer dauerhaft gespeicherten Lauf-ID, klaren Zustandsübergängen und einem wirkungsspezifischen Nachweis für den Abschluss. Danach lautet die Frage bei einer erneuten Verbindung nicht mehr: «Soll ich den Befehl noch einmal ausführen?», sondern: «Was sagt der Datensatz dieses Laufs, und was hat er verändert?»

Eine getrennte Verbindung hinterlässt drei ehrliche Antworten

Wenn ein SSH-Client einen Reset, ein Timeout, eine unterbrochene Pipe oder ein abruptes EOF meldet, befindet sich der Befehl in einem von drei Zuständen: Er wurde nie gestartet, er wurde gestartet und läuft noch, oder er wurde abgeschlossen. Der Exit-Code auf der Clientseite unterscheidet diese Fälle nicht zuverlässig.

Zwischen deiner Shell und dem entfernten Programm liegen mehrere Grenzen:

  • Deine lokale Shell startet ssh.
  • Der Client sendet eine SSH-Channel-Anfrage und die Befehlsbytes.
  • Der Server nimmt die Anfrage an und startet eine entfernte Shell oder ein Programm.
  • Dieses Programm führt die eigentliche Arbeit aus.
  • Das Programm wird beendet, und sshd sendet Ausgabe und Exit-Status zurück.

Ein Netzwerkabbruch nach jeder dieser Grenzen kann lokal einen Fehler erzeugen. Tritt er auf, bevor das entfernte Programm startet, ist nichts passiert. Tritt er auf, nachdem das Programm eine Änderung festgeschrieben hat, aber bevor der Exit-Status zurückkommt, ist die Änderung erfolgt, während dein Client weiterhin einen Fehler meldet.

Deshalb ist eine lokale Meldung wie Connection reset by peer ein Transportbeleg und kein fachlicher Beleg. Sie sagt, dass der Client die SSH-Unterhaltung nicht abschließen konnte. Sie sagt nicht, ob der entfernte Vorgang ausgeführt wurde.

Das Konfigurationshandbuch von OpenSSH weist in einem verwandten Zusammenhang auf inaktive Channels hin: Das Schließen einer Sitzung garantiert nicht, dass zugehörige Shell-Prozesse beendet wurden. Ein Channel-Timeout ist daher kein Mechanismus zur Jobsteuerung.

Schädlich wird es, wenn ein unklares Ergebnis als fehlgeschlagener Vorgang behandelt wird. Das ist verständlich, weil uns die meisten Kommandozeilenprogramme daran gewöhnen, einen Exit-Status ungleich null als «noch einmal ausführen» zu lesen. Ein Fehler im SSH-Transport durchbricht diese Abkürzung.

Befehlszustellung und Befehlsabschluss sind unterschiedliche Aussagen

Bei einem entfernten Befehl gibt es mindestens vier Aussagen, die sich nachweisen lassen sollten: Übermittlung, Start, Abschluss und Wirkung. Teams protokollieren oft nur eine davon und nehmen an, alle vier zu kennen.

Übermittlung bedeutet, dass der Client versucht hat, den Befehl zu senden. Dein lokales Terminal weiß das, aber es ist die schwächste Aussage. Start bedeutet, dass der entfernte Wrapper vor Beginn der Arbeit dauerhafte Belege angelegt hat. Abschluss bedeutet, dass der Wrapper ein endgültiges Ergebnis aufgezeichnet hat. Wirkung bedeutet, dass der gewünschte entfernte oder externe Zustand nun dem angeforderten Ergebnis entspricht.

Eine Prozessliste beweist weniger, als viele denken. Eine PID kann zeigen, dass jetzt ein ähnlich benannter Prozess existiert. Sie beweist nicht, dass er zu deiner Anfrage gehört, dass er den wichtigen Teil noch nicht festgeschrieben hat oder dass eine spätere Wiederholung sicher wäre. Nach dem Ende eines Prozesses machen wiederverwendete PIDs alte Datensätze noch unzuverlässiger.

Für Exit-Codes gilt eine ähnliche Einschränkung. POSIX definiert wait als Möglichkeit für eine Shell, den Status eines ihr bekannten Kindprozesses abzurufen. Diese Beziehung besteht innerhalb der entfernten Shell. Sobald die SSH-Verbindung verschwindet, hat die lokale Shell ihren Weg zu diesem Status verloren. Eine spätere SSH-Sitzung kann ihn nicht durch wait zurückholen. Sie muss einen Datensatz lesen, den der erste Lauf dauerhaft gespeichert hat.

Halte diese Aussagen in Runbooks und Automatisierungsausgaben getrennt:

  1. «Der Client konnte den Abschluss nicht bestätigen.»
  2. «Der Lauf r-20260722-1842-a91f wurde auf dem entfernten Host gestartet.»
  3. «Dieser Lauf hat den Exit-Status 0 aufgezeichnet.»
  4. «Die Deployment-Markierung meldet Release 2026.07.22.3

Die vierte Aussage beantwortet möglicherweise als einzige die eigentliche Betriebsfrage. Ein Dateikopiervorgang braucht eine Prüfsumme oder einen endgültigen Pfad mit dem erwarteten Inhalt. Eine Migration braucht die Schema-Version oder einen Eintrag im Migrationsprotokoll. Eine Anfrage an eine Zahlungs- oder Ticketing-API braucht einen Idempotenznachweis bei dieser API, nicht nur eine lokale Logzeile.

Lege die Lauf-ID auf dem entfernten Host an, bevor die Arbeit beginnt

Eine dauerhaft gespeicherte Lauf-ID verwandelt die unklare Arbeit nach einer erneuten Verbindung in eine einfache Abfrage. Erzeuge sie vor dem SSH-Aufruf, übergib sie an den entfernten Wrapper und lege jedes Artefakt unter einem daraus abgeleiteten Pfad ab.

Verwende nicht nur einen Zeitstempel. Zwei Agenten können in derselben Sekunde starten, Uhren können abweichen, und Zeitstempel sind schlechte undurchsichtige Kennungen. Kombiniere einen Zeitstempel mit Zufallsdaten oder nutze einen UUID-Generator aus deiner Umgebung. Die ID muss zur Prüfung erneut angegeben werden und in jedem wichtigen Log-Eintrag auftauchen.

Dieses Shell-Fragment erstellt ein Laufverzeichnis, schreibt den angeforderten Vorgang, legt eine Startmarkierung an und bewahrt beide Ausgabeströme auf. Es erwartet einen Befehl nach --. Halte den Wrapper an einem kontrollierten Ort wie /usr/local/sbin/run-recorded statt ihn für jede Befehlszeichenfolge neu zu kopieren.

#!/bin/sh
set -eu

run_id=$1
shift
[ "$1" = "--" ]
shift

base=/var/lib/recorded-runs
run_dir="$base/$run_id"

case "$run_id" in
  *[!A-Za-z0-9._-]*|'')
    printf '%s\n' "invalid run id" >&2
    exit 64
    ;;
esac

if ! mkdir "$run_dir" 2>/dev/null; then
  printf '%s\n' "run already exists: $run_id" >&2
  exit 75
fi

umask 077
printf '%s\n' "$*" > "$run_dir/request"
date -u +%Y-%m-%dT%H:%M:%SZ > "$run_dir/started_at"
printf '%s\n' "started" > "$run_dir/state"
printf '%s\n' "$$" > "$run_dir/pid"

set +e
"$@" >"$run_dir/stdout" 2>"$run_dir/stderr"
status=$?
set -e

printf '%s\n' "$status" > "$run_dir/exit_status"
date -u +%Y-%m-%dT%H:%M:%SZ > "$run_dir/finished_at"
printf '%s\n' "finished" > "$run_dir/state"
exit "$status"

Der Aufruf von mkdir erledigt mehr als reine Verwaltung. Das Erstellen schlägt fehl, wenn die Lauf-ID bereits existiert, und wirkt damit als einfache «nur einmal anlegen»-Sperre. So verhindern zwei Aufrufe mit derselben ID, dass die Arbeit unbemerkt doppelt ausgeführt wird. Gleichzeitige Arbeit mit verschiedenen IDs wird dadurch nicht verhindert. Dafür braucht es eine separate Sperre oder eine Einschränkung auf Anwendungsebene.

Die Reihenfolge ist wichtig. Der Wrapper schreibt started_at, state und pid, bevor er die Nutzlast ausführt. Er speichert exit_status, bevor er den Zustand auf finished setzt. Ein Prüfer, der finished ohne Exit-Status sieht, sollte den Datensatz als beschädigt und nicht als erfolgreich behandeln. Sieht er ein Laufverzeichnis ohne started_at, sollte er von einem unvollständigen Einrichtungsfehler ausgehen.

Schreibe finished nicht über einen Shell-Trap und betrachte die Sache dann als erledigt. Ein plötzlicher Host-Ausfall, ein Speicherfehler, ein erzwungenes Beenden oder ein Problem mit dem Dateisystem kann verhindern, dass Traps ausgeführt werden. Eine vorhandene Abschlussmarkierung ist ein Beleg. Ihr Fehlen beweist nicht, dass die Nutzlast nicht fertig wurde.

Prüfe einen Lauf in einer Reihenfolge, die dich nicht täuscht

Verbinde dich zuerst mit einem schreibgeschützten Statusbefehl erneut. Starte dabei nicht die Nutzlast mit denselben Argumenten ein zweites Mal und hoffe, dass die Antwort dadurch klarer wird.

Ein guter Prüfer sollte den Datensatz als absent, running, finished oder damaged einordnen. Dieses Beispiel verwendet das obige Verzeichnisformat und gibt Fakten aus, die ein Mensch oder ein Agent bewerten kann.

#!/bin/sh
set -eu

run_id=$1
run_dir="/var/lib/recorded-runs/$run_id"

if [ ! -d "$run_dir" ]; then
  printf '%s\n' 'state=absent'
  exit 0
fi

if [ ! -f "$run_dir/started_at" ]; then
  printf '%s\n' 'state=damaged reason=missing-start-marker'
  exit 2
fi

if [ -f "$run_dir/finished_at" ] && [ -f "$run_dir/exit_status" ]; then
  printf '%s\n' 'state=finished'
  printf 'exit_status=%s\n' "$(cat "$run_dir/exit_status")"
  printf 'started_at=%s\n' "$(cat "$run_dir/started_at")"
  printf 'finished_at=%s\n' "$(cat "$run_dir/finished_at")"
  exit 0
fi

if [ -f "$run_dir/pid" ]; then
  pid=$(cat "$run_dir/pid")
  if kill -0 "$pid" 2>/dev/null; then
    printf 'state=running pid=%s\n' "$pid"
    exit 0
  fi
fi

printf '%s\n' 'state=damaged reason=no-finish-record-and-pid-not-live'
exit 2

Führe ihn als neuen SSH-Befehl aus:

ssh ops@host /usr/local/sbin/check-recorded-run r-20260722-1842-a91f

Die Ausgabe sollte wie eine dieser Varianten aussehen:

state=absent
state=running pid=48192
state=finished
exit_status=0
started_at=2026-07-22T18:42:19Z
finished_at=2026-07-22T18:47:03Z

Der unbequeme Zustand damaged gehört ins Protokoll. Lässt du ihn weg, muss der Prüfer fehlende Belege in eine erfreuliche Vermutung verwandeln. Wenn der Host während der Ausführung neu gestartet wurde, schlägt kill -0 fehl und es gibt keine Abschlussmarkierung. Die richtige Reaktion besteht darin, die beabsichtigte Wirkung und die Anwendungslogs zu prüfen und anschließend zu entscheiden, ob eine Korrektur nötig ist.

Verwende im Prüfer nicht ps | grep. Dabei werden unabhängige Prozesse gefunden, sich ändernde Befehlsnamen und unterschiedliche Ausgabeformate sorgen für weitere Probleme. kill -0 ist nur ein Hinweis darauf, dass eine aufgezeichnete PID noch lebt. Es ist kein Abschlussnachweis. Deshalb prüft der Prüfer zuerst die endgültigen Artefakte und erst danach die PID.

Auch ein abgeschlossener Exit-Status beweist nicht unbedingt die gewünschte Wirkung

Jeden sensiblen Befehl genehmigen
Markiere sensible SSH-Schlüssel so, dass jede Verwendung eine Genehmigung erfordert, einschließlich der Bestätigung per Mausklick oder Touch ID.

Der abschließende Datensatz des Wrappers beweist, was der Wrapper beobachtet hat, aber nicht zwingend, was die Außenwelt akzeptiert hat. Das wird bei Befehlen deutlich, die Anfragen senden.

Stell dir ein entferntes Skript vor, das über eine API einen DNS-Eintrag erstellt und anschließend exit_status=0 schreibt. Das Skript kann von der API Erfolg erhalten, bevor ein Resolver den neuen Eintrag sieht. Ein Deployment-Skript kann erfolgreich beendet werden, nachdem es einen Rollout eingereicht hat, der später an den Health-Checks scheitert. Ein Datenbankwerkzeug kann eine erfolgreiche Verbindung melden, obwohl eine Anweisung in einer mehrstufigen Prozedur festgeschrieben wurde und eine spätere Anweisung fehlschlägt.

Jeder Vorgang braucht eine Nachbedingung, die zu seiner Wirkung passt. Die Nachbedingung sollte sich sicher wiederholt lesen lassen und spezifisch genug sein, um ein altes oder unabhängiges Ergebnis auszuschließen.

Schreibe bei einem Release die Lauf-ID in ein Release-Manifest und frage die aktive Version beim Dienst ab. Bei einer Datenbankänderung prüfst du die Migrationstabelle auf die Migrationskennung und die Prüfsumme. Bei einem erzeugten Artefakt vergleichst du einen vorab berechneten SHA-256-Hash, nachdem die Datei an ihrem endgültigen Pfad abgelegt wurde. Bei einer API-Anfrage verwendest du das Idempotenz-Token des Anbieters, sofern vorhanden, und fragst anschließend die entstandene Ressource über dieses Token oder über eine gespeicherte Anfrage-ID ab.

Das schlechteste Design ist ein Skript, das nach dem Senden einer Anfrage «fertig» ausgibt und dieses Wort anschließend als Beleg behandelt. Die stdout-Datei zeigt, was ein Prozess ausgegeben hat. Eine Nachbedingung zeigt, was das System jetzt enthält.

Diese Unterscheidung zeigt auch, wann sich ein Vorgang nicht sicher allein über SSH automatisieren lässt. Wenn der entfernte Befehl einen Drittanbieterdienst ohne Idempotenzkontrolle aufruft und sich eine frühere Anfrage nicht suchen lässt, kann ein unterbrochener Aufruf möglicherweise nicht eingeordnet werden. Schalte dann eine manuelle Genehmigung oder einen ausgleichenden Prozess dazwischen. Weitere Wiederholungen erzeugen keine fehlenden Belege.

Idempotenz ist besser als Wiederherstellungstheater

Ein Prüfprotokoll verringert die Unsicherheit. Ein idempotentes Befehlsdesign verringert die Kosten dieser Unsicherheit. Du brauchst beides.

Ein idempotenter Vorgang erreicht bei erneuter Anwendung mit derselben Anfrage denselben Zielzustand. mkdir -p /srv/app/cache entspricht diesem Modell weitgehend. useradd deploy tut das nicht, sofern das Skript nicht zuerst prüft, ob das vorhandene Konto die erwarteten Eigenschaften besitzt. curl -X POST /orders ist ebenfalls nicht idempotent, außer der Dienst versteht ein Idempotenz-Token und behandelt ein wiederholtes Token als dieselbe Anfrage.

Verwechsle «der Befehl tut beim zweiten Lauf wahrscheinlich nichts» nicht mit Idempotenz. Ein Deployment kann eine Datei zweimal auf dieselbe Weise überschreiben und dabei zweimal einen Neustart auslösen. Ein Migrationstool kann seine eigene Historie erkennen und trotzdem eine gefährliche Initialisierung ausführen, bevor es prüft. Lies das Verhalten des Befehls und teste den Unterbrechungsfall.

Baue Anfragen um eine stabile Vorgangskennung. Übergib dieselbe ID an den entfernten Wrapper und, wenn möglich, an das Zielsystem. Ein entfernter Release-Wrapper könnte /var/lib/recorded-runs/$run_id/effect erst anlegen, wenn der aktive Release-Endpunkt die angeforderte Version meldet. Ein Aufruf einer Bereitstellungs-API könnte run_id als Idempotenzwert verwenden. Eine SSH-Wiederholung kann dann beide Systeme nach derselben Arbeitseinheit fragen.

Eine praktische Regel lautet: Wiederhole Lesevorgänge unbeschränkt, wiederhole Erstellungen nur mit einer dauerhaft gespeicherten Eindeutigkeitsbedingung, und wiederhole mehrstufige Änderungen erst, nachdem ihre Nachbedingung den früheren Lauf eingeordnet hat. Das ist langsamer als ein blindes erneutes Senden. Es ist deutlich schneller, als doppelte Infrastruktur aufzuräumen.

Das Ausführen im Hintergrund verschiebt das Problem nur

Das Gateway auf dem Mac behalten
Betreibe den Tresorkern in einer signierten Mac-Menüleisten-App, ohne einen separaten Daemon verwalten zu müssen.

nohup, &, disown, tmux, screen und Service-Manager lösen jeweils einen anderen Teil des Problems. Keines dieser Werkzeuge verwandelt eine unsichere entfernte Anfrage in ein geprüftes Ergebnis.

nohup hilft einem Prozess in üblichen Shell-Umgebungen dabei, ein Hangup-Signal zu überleben. Ein einfaches nohup task & hinterlässt trotzdem nur Ausgabedateien, eine PID und keinen strukturierten Abschlussdatensatz, sofern du keinen hinzufügst. Außerdem entsteht eine neue Unklarheit: Hat die entfernte Shell nohup gestartet oder brach die Verbindung vorher ab?

tmux und screen halten eine interaktive Umgebung am Leben. Sie eignen sich gut, wenn ein Operator sich erneut verbinden und einen langen Befehl manuell prüfen muss. Als Automatisierungsvertrag funktionieren sie schlecht, weil Sitzungsnamen kollidieren können, Scrollback kein Ergebnisschema ist und ein abgetrenntes Terminal einem anderen System nicht sagt, ob die gewünschte Wirkung eingetreten ist.

Ein Service-Manager ist besser geeignet, wenn die Arbeit tatsächlich ein Dienst oder ein eingereihter Job ist. Ein entfernter Befehl kann etwa eine benannte Einheit einreichen, während eine spätere Abfrage deren Lebenszyklus und Logs prüft. Nutze dieses Modell, wenn der Host bereits einen betrieblichen Verantwortlichen für Jobs hat. Füge einem fünf Sekunden dauernden Verwaltungsbefehl nicht nur deshalb einen Service-Manager hinzu, weil du keinen kleinen Laufdatensatz schreiben willst.

Die sinnvolle Aufteilung ist einfach. Interaktive Reparaturarbeiten gehören in einen Terminal-Multiplexer. Geplante oder langlebige Workloads gehören unter einen Service-Manager. Für imperative Befehle, bei denen ein SSH-Aufrufer nach einer erneuten Verbindung eine verlässliche Antwort braucht, eignet sich ein aufgezeichneter Wrapper.

Keepalives verkürzen das Warten, schließen das Zeitfenster der Unklarheit aber nicht

Keepalives des OpenSSH-Clients machen tote Verbindungen schneller sichtbar. Sie garantieren nicht, dass ein Befehl nicht angenommen wurde, bevor der Netzwerkpfad ausfiel.

Für Hosts, bei denen ein festhängender Client Zeit kostet, ist eine Client-Konfiguration wie diese sinnvoll:

Host production-*
    ServerAliveInterval 20
    ServerAliveCountMax 3
    TCPKeepAlive yes

ServerAliveInterval sendet Nachrichten auf Anwendungsebene durch den verschlüsselten SSH-Channel, wenn keine Daten eintreffen. Erhält der Client nicht genügend Antworten, beendet er sich, statt unbegrenzt zu warten. OpenSSH beschreibt dies getrennt von TCP-Keepalives, die auf der Transportebene arbeiten.

Nutze die Einstellung, um zu begrenzen, wie lange ein Aufrufer wartet, bevor er mit der Prüfung beginnt. Beschreibe sie nicht als Garantie für die Befehlszustellung. Die Verbindung kann weiterhin abbrechen, nachdem der entfernte Host den Befehl angenommen hat, aber bevor der Client das Ergebnis erhält.

Für Multiplexing gilt dieselbe Vorsicht. ControlMaster und ControlPersist können eine bestehende Netzwerkverbindung für mehrere SSH-Befehle wiederverwenden. Das senkt den Einrichtungsaufwand, aber ein defekter Master kann mehrere Aufrufe gleichzeitig beeinträchtigen. Im OpenSSH-Handbuch steht, dass ein persistierter Master nach dem Ende des ursprünglichen Clients im Hintergrund bleibt. Das ist betrieblich nützlich, liefert aber keinen Abschlussnachweis für einen über ihn gesendeten Befehl.

Für Automatisierung solltest du ein explizites Verbindungs-Timeout und zur Umgebung passende Grenzen für die Erreichbarkeit festlegen. Der Prüfpfad muss unabhängig von der ursprünglichen SSH-Sitzung sein. Ein schneller Fehler hilft nur, wenn die nächste Aktion eine Statusabfrage und keine blinde Wiederholung ist.

Entwickle den Fehlerfall, bevor du ihn um zwei Uhr morgens brauchst

SSH-Schlüssel von Agenten fernhalten
Leite SSH-Befehle von Agenten über Sallyport weiter, während der SSH-Schlüssel in seinem verschlüsselten Tresor bleibt.

Ein Protokoll, das noch nie unterbrochen wurde, ist nur ein Entwurf. Teste es mit einem sicheren, aber ausreichend langsamen Vorgang, bei dem du die Verbindung an verschiedenen Stellen trennen kannst.

Beginne mit einer Nutzlast, die eine nummerierte Fortschrittsdatei schreibt, zwischen den Stufen wartet und eine abschließende Wirkungsmarkierung anlegt. Starte sie über den Wrapper. Beende den lokalen SSH-Client, sobald die entfernte started_at-Markierung erscheint, verbinde dich erneut und führe den Prüfer aus. Wiederhole den Test, indem du die entfernte Nutzlast beendest, bevor sie finished_at schreibt. Simuliere schließlich, sofern deine Umgebung das erlaubt, einen Neustart des Hosts.

Die erwarteten Einstufungen sollten eindeutig sein:

  • Bevor der Wrapper die Lauf-ID beansprucht, liefert die Prüfung absent.
  • Während die Nutzlast ausgeführt wird, liefert die Prüfung running.
  • Nach einem normalen Abschluss liefert sie finished mit dem aufgezeichneten Status.
  • Nach einer erzwungenen Unterbrechung oder einem Host-Ausfall liefert sie damaged, gefolgt von einer Prüfung der Nachbedingung.

Teste auch doppelte Übermittlung. Rufe dieselbe Lauf-ID zweimal fast gleichzeitig auf. Ein Aufruf muss die Verzeichnis-Sperre gewinnen, während der andere ein eindeutiges Duplikatergebnis zurückgibt, ohne die Nutzlast auszuführen. Probiere danach zwei verschiedene IDs aus, die dieselbe Ressource ansprechen. Wenn dadurch ein Wettlauf entsteht, braucht der Wrapper eine ressourcenspezifische Sperre oder das Zielsystem eine Eindeutigkeitsregel.

Bewahre Laufdatensätze lange genug auf, um dein betriebliches Wiederholungsfenster abzudecken. Wenn ein Job einen Tag später wiederholt werden kann, der Host die Datensätze aber nach einer Stunde löscht, hast du einen Timer in deine Unsicherheit eingebaut. Schütze die Datensätze außerdem vor beiläufigen Änderungen. Das Konto, das einen Lauf prüft, sollte exit_status nicht bearbeiten oder das Nutzlast-Log ersetzen können. Auf gemeinsam genutzten Systemen sollten Einreicher, Ausführer und Leser nach Möglichkeit getrennte Rollen haben.

Wenn KI-Agenten SSH aufrufen, müssen Zugangsdatenisolierung und Befehlsprüfung zusammenspielen. Sallyport bewahrt SSH-Zugangsdaten in seinem Tresor auf und kann die von einem Agenten angeforderte Aktion aufzeichnen. Der entfernte Wrapper liefert zusätzlich die dauerhaft gespeicherte Antwort über den Job selbst. Der Aktionsdatensatz zeigt, welcher Prozess den Befehl angefordert hat. Die entfernte Lauf-ID zeigt, was passierte, nachdem die Verbindung unklar wurde.

Die sichere Entscheidung über eine Wiederholung hat vier Ergebnisse

Ordne den Zustand nach einer Trennung ein, bevor du handelst. Es gibt vier nützliche Ergebnisse, und nur eines davon ist eine automatische Wiederholung.

Wenn der Lauf absent ist, hat der entfernte Wrapper keinen Datensatz angelegt. Du kannst denselben Vorgang mit derselben Lauf-ID einreichen, sofern du dem «nur einmal anlegen»-Verhalten des Wrappers vertraust. Wenn der Befehl auch außerhalb des Wrappers ausgeführt worden sein könnte, prüfe zuerst das Ziel. Der Wrapper kann nicht beweisen, was an ihm vorbei ausgeführt wurde.

Wenn der Lauf running ist, warte oder brich ihn über einen vorgangsspezifischen Kontrollpfad ab. Starte keine zweite Kopie. Eine Timeout-Regel gehört in die Nutzlast oder den Job-Manager, nicht in einen zweiten SSH-Aufruf, der mit dem ersten um die Ausführung konkurriert.

Wenn der Lauf finished ist, prüfe seinen Exit-Status und anschließend die Nachbedingung, sofern der Vorgang eine relevante externe Wirkung hat. Ein Exit-Status von null zusammen mit einer fehlgeschlagenen Nachbedingung bedeutet einen fehlgeschlagenen Vorgang. Behandle die Wirkungsprüfung als maßgeblich.

Wenn der Lauf damaged ist, nenne es nicht länger ein Wiederholungsproblem. Es ist eine Abgleichsarbeit. Prüfe Logs, Journal-Einträge, den Zielzustand und alle Idempotenznachweise. Entscheide, ob du die unvollständige Wirkung reparierst, den Vorgang als abgeschlossen markierst oder einen neuen Lauf einreichst, der den beobachteten Zustand ausdrücklich behandelt. Dafür kann ein Mensch nötig sein, weil der fehlende Datensatz den Beleg entfernt hat, auf den die Automatisierung angewiesen ist.

Die lohnende Änderung ist klein: Jeder Befehl, dessen Wiederholung schaden kann, bekommt eine Lauf-ID, einen entfernten Startdatensatz, einen entfernten Abschlussdatensatz und eine Prüfung des Zielzustands. SSH wird weiterhin abbrechen. Deine Automatisierung muss dann aber nicht mehr so tun, als wüsste sie, was passiert ist.

FAQ

Was bedeutet ein unbekannter Status auf dem entfernten Host nach einer SSH-Trennung?

Das bedeutet, dass der SSH-Client die Verbindung verloren hat, nachdem er die Anfrage möglicherweise gesendet hatte, aber bevor er ein verlässliches Ergebnis erhalten konnte. Der entfernte Host hat den Befehl vielleicht nie gestartet, führt ihn möglicherweise noch aus oder hat ihn bereits beendet. Behandle alle drei Möglichkeiten als offen, bis entfernte Belege sie ausschließen.

Wurde mein entfernter Befehl ausgeführt, wenn die SSH-Verbindung abgebrochen ist?

Nein. Ein erfolgreicher lokaler Schreibvorgang beweist nur, dass dein Client Bytes an seinen lokalen Netzwerk-Stack übergeben hat. Ein Timeout oder Reset danach sagt nicht, ob sshd die Channel-Anfrage angenommen oder die entfernte Shell den Befehl gestartet hat.

Wie prüfe ich, ob ein Befehl nach einem SSH-Abbruch noch läuft?

Verbinde dich erneut und prüfe einen dauerhaft gespeicherten Laufdatensatz, nicht nur die Prozessliste. Suche nach einem Laufverzeichnis, einer Startmarkierung, einer Ergebnisdatei, erwarteten Ausgaben und einer wirkungsspezifischen Prüfung, etwa einer Datenbankzeile, einer Release-Version oder einer Objekt-Prüfsumme.

Ist es sicher, einen SSH-Befehl nach einem Timeout erneut auszuführen?

In der Regel nein. Das erneute Ausführen eines Befehls mit externen Auswirkungen kann doppelte Datensätze erzeugen, neuere Daten überschreiben, doppelte Anfragen senden oder eine zweite Migration starten. Wiederhole einen Befehl nur, wenn er idempotent ausgelegt ist oder der erste Lauf nachweislich nicht gestartet wurde.

Löst nohup Probleme mit abgebrochenen SSH-Verbindungen?

nohup ändert nur, wie ein Prozess in typischen Umgebungen mit Hangup-Signalen umgeht, und leitet Ausgaben um. Es erzeugt weder eine dauerhaft gespeicherte Lauf-ID noch erfasst es zuverlässig den finalen Status oder macht eine erneute Ausführung sicher.

Sollte ich für lange SSH-Befehle tmux oder screen verwenden?

tmux und screen helfen dabei, eine interaktive Shell über eine erneute Verbindung hinweg zu erhalten. Das ist bei manuellen Wartungsarbeiten nützlich. Sie beweisen jedoch nicht, ob eine externe Änderung erfolgt ist, und eignen sich für automatisierte Arbeit nur schlecht als Audit-Nachweis.

Können SSH-Keepalives einen unbekannten entfernten Status verhindern?

Nutze Keepalives, um einen unterbrochenen Netzwerkpfad schneller zu erkennen, nicht als Garantie für Zustellung oder Abschluss. ServerAliveInterval und ServerAliveCountMax von OpenSSH helfen dem Client, das Warten auf eine tote Verbindung zu beenden. Sie können eine bereits unklare Befehlserteilung aber nicht nachträglich klären.

Warum reicht eine PID nicht als Beleg dafür, dass ein entfernter Job abgeschlossen ist?

Eine PID identifiziert einen Prozess zu einem bestimmten Zeitpunkt und kann nach dessen Ende wiederverwendet werden. Speichere sie als Diagnosehinweis zusammen mit einer Lauf-ID, der Startzeit, der Befehlsversion, einer Ergebnisdatei und einem wirkungsspezifischen Abschlussnachweis.

Wann sollte ich einen Wrapper für entfernte Befehle erstellen?

Nutze einen Remote-Wrapper, wenn der Befehl Daten verändert, Software ausrollt, Zugangsdaten rotiert, Infrastruktur steuert oder Kosten verursacht. Ein einfacher entfernter Befehl genügt für harmlose Lesevorgänge. Sobald eine Wiederholung Schaden anrichten könnte, rechtfertigt ein Wrapper seinen zusätzlichen Aufwand.

Kann ein KI-Agent SSH-Befehle, die möglicherweise unterbrochen werden, sicher ausführen?

Sallyport kann SSH-Aktionen ausführen, ohne dem Agenten den SSH-Schlüssel offenzulegen, und zeichnet die einzelne Aktion auf. Das entfernte Programm braucht trotzdem ein Abschlussprotokoll. Die Kontrolle über Zugangsdaten und ein unbekannter Status auf dem entfernten Host sind getrennte Probleme. Wer das eine als Ersatz für das andere betrachtet, schafft trügerische Sicherheit.

Sallyport

Sallyport führt API-Aufrufe und SSH-Befehle für Ihren KI-Agenten aus. Die Schlüssel bleiben in einem lokalen Tresor auf Ihrem Mac; Sie geben jeden Lauf frei, und jede Aktion landet in einem versiegelten Journal.

© 2026 Sallyport · Open Source unter Apache-2.0 · Oleg Sotnikov