7 Min. Lesezeit

Teilweise fehlgeschlagene SSH-Befehle: Agenten sollen Arbeit nicht wiederholen

Teilweise fehlgeschlagene SSH-Befehle brauchen eine Zustandsprüfung statt blinder Wiederholungen. Erfasse Exit-Codes, Checkpoints, Quittungen und den beobachteten Remote-Zustand für KI-Agenten.

Teilweise fehlgeschlagene SSH-Befehle: Agenten sollen Arbeit nicht wiederholen

Ein KI-Agent muss einen fehlgeschlagenen SSH-Befehl als unbekannten Zustandsübergang behandeln, nicht als Erlaubnis, denselben Text erneut auszuführen. Ein Befehl kann einen Benutzer anlegen, einen Dienst neu laden und anschließend beim Schreiben einer Abschlussdatei scheitern. Eine blinde Wiederholung kann ein doppeltes Konto anlegen, eine von Hand angepasste Einstellung überschreiben oder eine reparierbare Bereitstellung in einen Ausfall verwandeln.

Der übliche Rat «Prüfe den Exit-Code» ist notwendig, reicht aber nicht aus. Der Exit-Status beschreibt, wie ein Prozess beendet wurde. Für die Wiederherstellung brauchst du einen Datensatz darüber, was der Prozess abgeschlossen hat, welchen Zustand der Host jetzt meldet und welche Operation der Agent ausführen wollte. Lege diese Informationen in einer dauerhaften Befehlsquittung ab und lasse den Agenten vor jeder weiteren Aktion den Zustand abgleichen.

Ein SSH-Fehler hinterlässt drei verschiedene Unklarheiten

Eine fehlgeschlagene SSH-Aktion kann bedeuten, dass die Remote-Shell fehlgeschlagen ist, die Verbindung abgebrochen wurde oder der Controller nicht länger gewartet hat. Diese Fälle erfordern unterschiedliche Reaktionen. Agenten fassen sie trotzdem oft einfach als «Befehl fehlgeschlagen» zusammen.

Nehmen wir ein Remote-Bereitstellungsskript, das diese Schritte nacheinander ausführt:

  1. Es schreibt ein neues Anwendungsarchiv in ein Release-Verzeichnis.
  2. Es ändert einen current-Symlink auf dieses Release.
  3. Es startet den Dienst neu.
  4. Es führt einen Health-Check aus und beendet sich mit einem Status ungleich null, weil der Check einen vorübergehenden Fehler bei einer Abhängigkeit feststellt.

Das Release ist aktiv, obwohl der Befehl einen Fehler zurückgegeben hat. Eine Wiederholung des Skripts kann harmlos sein, wenn jede Operation Wiederholungen verträgt. Häufiger legt das Skript jedoch ein neues Release-Verzeichnis an, leert ein Log, rotiert ein Zugangstoken oder führt eine Migration aus. Der Exit-Code allein kann nicht klären, was passiert ist.

Noch problematischer ist eine Unterbrechung des SSH-Transports. Der lokale Prozess kann nach einem Netzwerkabbruch 255 erhalten, während die Remote-Shell weiterläuft. Bei einem Timeout des Controllers tritt dasselbe Problem auf. Der Controller weiß nur, dass ihm eine abschließende Antwort fehlt. Er weiß nicht, ob das Ziel die Anfrage erhalten hat, ob die Shell gestartet wurde oder ob der Prozess noch Änderungen vornimmt.

Diese Unterscheidung bestimmt die nächste Aktion des Agenten:

  • Ein bestätigter Remote-Exit mit Quittung erfordert eine Wiederherstellung auf Grundlage des fehlgeschlagenen Checkpoints.
  • Bei einem Transportfehler muss der Zustand vor jeder Änderung beobachtet werden.
  • Ein Controller-Timeout erfordert Beobachtung und bei Bedarf ein ausdrückliches Abbruchverfahren statt einer doppelten Anfrage.

Bezeichne nicht alle drei Fälle als Wiederholungen. Eine Wiederholung ist eine Operation mit einer bekannten Regel für sichere erneute Ausführung. Ein unbekannter Remote-Zustand braucht eine Zustandsprüfung.

Der Exit-Status beschreibt einen Prozess, keine Transaktion

Ein SSH-Exit-Code liefert nützliche Hinweise, ist aber kein Commit-Datensatz einer Datenbank. Im OpenSSH-Handbuch steht, dass ssh den Exit-Status des Remote-Befehls zurückgibt oder 255, wenn ein Fehler aufgetreten ist. Diese Formulierung zieht eine Grenze, die viele Automatisierungssysteme übersehen: Ein Remote-Exit-Status beschreibt den Befehl, sobald SSH ihn empfängt, während 255 den eigenen Fehlerpfad von SSH abdeckt.

Auch ein Exit-Code von null muss interpretiert werden. In der POSIX-Shell stammt der Status einer einfachen sequenziellen Befehlsliste normalerweise vom letzten Befehl. Dieses Skript kann nach einem wichtigen Fehler Erfolg melden:

install -m 0644 app.conf /etc/myapp/app.conf
systemctl restart myapp
logger -t deploy "deployment finished"

Wenn install fehlschlägt, aber systemctl restart und logger den Status null zurückgeben, ist auch der abschließende Status des Skripts null. Der Agent sieht Erfolg und kann fälschlich behaupten, die Konfiguration sei geändert worden. Ein abschließendes echo done erzeugt dieselbe Täuschung.

Pipelines eröffnen einen weiteren Fehlerpfad. Im Bash Reference Manual steht, dass der Status einer Pipeline dem Status des letzten Befehls entspricht, sofern pipefail nicht aktiviert ist. Dieser Befehl kann null zurückgeben, wenn die Extraktion sauber endet, obwohl sie keine brauchbaren Daten erhalten hat:

curl --fail --silent https://example.invalid/build.tar.gz | tar -xz -C /srv/myapp

Verwende einen expliziten Interpreter und lege das gewünschte Verhalten fest:

#!/usr/bin/env bash
set -Eeuo pipefail

curl --fail --silent --show-error "$archive_url" | tar -xz -C "$release_dir"

-e weist Bash an, bei vielen unbehandelten Fehlern anzuhalten, -u lehnt nicht gesetzte Variablen ab und pipefail bewahrt Fehler früherer Pipeline-Elemente. Die Option E sorgt dafür, dass ein ERR-Trap auch in Funktionen und Kommando-Substitutionen gilt. Diese Einstellungen verbessern die Fehlererkennung. Sie machen eine Sequenz jedoch nicht atomar.

Dieser letzte Punkt ist wichtig. set -e greift erst, nachdem eine Operation einen Fehler zurückgegeben hat. Es kann kein Verzeichnis entfernen, das ein vorheriger Befehl angelegt hat, und keinen Dienst zurücksetzen, den ein vorheriger Befehl neu gestartet hat. Außerdem gibt es Ausnahmen, die oft überraschen: Befehle in if-Prüfungen, auf der linken Seite von && oder || sowie in verschiedenen zusammengesetzten Konstruktionen beenden die Shell nicht immer. Schreibe für Aktionen, deren Fehler die Wiederherstellung beeinflusst, ausdrückliche Prüfungen.

Lege die Grenze der Operation fest, bevor du den Befehl schreibst

Ein Agent kann eine vage Anweisung wie «Stelle den Dienst bereit» nicht zuverlässig wiederherstellen. Der Remote-Befehl braucht eine benannte Operation mit einer Nachbedingung, die ein Beobachter prüfen kann.

Bei einer Release-Änderung könnte die Operation lauten: «Setze /srv/myapp/current auf Release 2025-04-18.3 und bestätige anschließend, dass der aktive Dienst dieses Release meldet.» Bei einer Datenbankänderung könnte sie lauten: «Wende die Migration add_invoice_index genau einmal an und bestätige, dass ihr Migrationseintrag vorhanden ist.» Der Befehlstext ist ein Implementierungsdetail. Die Operation und ihre Nachbedingung entscheiden darüber, ob eine Wiederherstellung möglich ist.

Teile eine Operation an irreversiblen oder nach außen sichtbaren Grenzen auf. Ein sinnvoller Checkpoint ist nicht jede einzelne Shell-Zeile. Zeichne einen Checkpoint nach einer Zustandsänderung auf, die die nächste Entscheidung beeinflussen würde. Bei einer Bereitstellung können das ein geprüftes Archiv, ein gefülltes Release-Verzeichnis, ein umgeschalteter Symlink, ein Neustart des Dienstes und ein beobachteter Health-Zustand sein.

Vermeide die verbreitete, aber falsche Empfehlung, jeden Remote-Befehl «idempotent» zu machen und ihn dann endlos zu wiederholen. Idempotenz gilt für eine bestimmte Operation und einen festgelegten Zielzustand. mkdir -p /srv/app kann wiederholbar sein. useradd deploy ist nur dann wiederholbar, wenn der Agent prüft, ob das vorhandene Konto die erwartete UID, Gruppe, das erwartete Home-Verzeichnis und die erwartete Shell besitzt. ALTER TABLE kann bei einer zweiten Ausführung fehlschlagen. Schlecht geschriebene Migrationen können eine verwandte Änderung sogar zweimal anwenden.

Ein Befehl kann sicher wiederholbar sein, während der umgebende Ablauf es nicht ist. Einen Dienst neu zu starten, kann wiederholbar sein. Wird er jedoch während des Kopierens einer Konfiguration neu gestartet, kann eine unvollständige Datei sichtbar werden. Platziere die Zustandsprüfung direkt neben der Aktion. Verlasse dich nicht darauf, dass ein Agent sie aus einer allgemeinen Regel ableitet.

Definiere für jede Operation vier Felder, bevor du Zugriff gewährst:

  • Eine Operations-ID, die während der Wiederherstellung gleich bleibt.
  • Eine gewünschte Nachbedingung, die ein Nur-Lese-Befehl prüfen kann.
  • Checkpoints, die abgeschlossene Zustandsänderungen beschreiben.
  • Eine Wiederherstellungsaktion für jeden unvollständigen Checkpoint.

Die Operations-ID ist nicht bloß Kosmetik. Wenn der Controller bei jedem Versuch eine neue Kennung erzeugt, kann das Ziel eine Fortsetzung nicht von einer neuen Anfrage unterscheiden. So entstehen wiederholte Migrationen und doppelte Bereitstellungen.

Schreibe vor und nach jeder Zustandsänderung eine Quittung

Eine dauerhafte Quittung macht einen Teilfehler zu einem prüfbaren Ereignis. Schreibe sie vor der ersten Änderung auf dem Zielhost, aktualisiere sie nach jedem wichtigen Checkpoint und führe die Aktualisierungen atomar aus.

Das folgende Bash-Skript ist bewusst schlicht gehalten. Es stellt ein bereits vorbereitetes Release bereit, indem es einen Symlink umschaltet und einen Systemdienst neu startet. Es löst nicht jede Bereitstellungsmethode. Es zeigt die Quittungsmechanik, die ein Agent braucht.

#!/usr/bin/env bash
set -Eeuo pipefail

operation_id=${1:?operation ID required}
release=${2:?release path required}
service=${3:?service name required}
state_dir=/var/lib/agent-ops
receipt="$state_dir/$operation_id.receipt"
tmp="$receipt.$$"

mkdir -p "$state_dir"
chmod 0700 "$state_dir"

write_receipt() {
  cat >"$tmp" <<EOF
operation_id=$operation_id
release=$release
service=$service
checkpoint=$1
updated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
EOF
  chmod 0600 "$tmp"
  mv -f "$tmp" "$receipt"
}

fail() {
  status=$?
  write_receipt "failed:$status"
  exit "$status"
}
trap fail ERR

if [[ -f "$receipt" ]]; then
  . "$receipt"
  case "$checkpoint" in
    complete)
      printf 'operation already complete: %s\n' "$operation_id"
      exit 0
      ;;
    switched|restarted)
      printf 'operation requires reconciliation: %s\n' "$checkpoint" >&2
      exit 75
      ;;
  esac
fi

[[ -d "$release" ]]
write_receipt "release_verified"

ln -sfn "$release" /srv/myapp/current
write_receipt "switched"

systemctl restart "$service"
write_receipt "restarted"

active_target=$(readlink -f /srv/myapp/current)
[[ "$active_target" == "$release" ]]
systemctl is-active --quiet "$service"
write_receipt "complete"
printf 'operation complete: %s\n' "$operation_id"

Die temporäre Datei und mv sind wichtig. Auf einem einzelnen Dateisystem ersetzt das Umbenennen die Quittung in einer Operation. Ein Leser erhält dadurch entweder die vorherige vollständige Quittung oder die neue vollständige Quittung, niemals eine halbe Datei. Das Zustandsverzeichnis sollte nur für das Konto beschreibbar sein, dem die Operation gehört. Kann ein nicht vertrauenswürdiger Benutzer Quittungen bearbeiten, akzeptiert die Wiederherstellungslogik des Agenten erfundene Angaben als Beweis.

Der ERR-Trap zeichnet den Exit-Status auf, wenn Bash einen Fehler behandelt. Er kann nicht laufen, wenn die Maschine die Stromversorgung verliert, ein nicht abfangbares Signal empfängt oder abrupt ausfällt. Deshalb zeichnet das Skript den Fortschritt nach jeder abgeschlossenen Zustandsänderung auf, statt sich nur auf einen abschließenden Trap zu verlassen.

Übernimm nicht beliebige Quittungsformate wie in diesem kleinen Beispiel per source, es sei denn, Verzeichnisbesitz und Berechtigungen sind streng eingeschränkt. In der Produktion solltest du JSON mit einem bekannten Parser oder ein festes Zeilenformat verwenden, das unerwartete Felder ablehnt. Das Beispiel bindet nur eine Datei ein, die es gerade in einem geschützten Verzeichnis erstellt hat, damit der Shell-Code lesbar bleibt.

Die Quittung sollte beobachtete Fakten enthalten, keine optimistischen Absichten. checkpoint=switched bedeutet, dass der Symlink-Befehl erfolgreich zurückgekehrt ist. Es bedeutet nicht, dass der Dienst das neue Release geladen hat. complete folgt erst auf die ausdrücklichen Prüfungen der Nachbedingung. Diese Unterscheidung verhindert, dass ein Agent einen geschriebenen Befehl mit einer abgeschlossenen Operation verwechselt.

Der Agent soll eine Zustandsprüfung anfordern, keinen neuen Befehl

Sehen, welcher Prozess fortgesetzt wird
Genehmige einen neuen Agent-Prozess einmal und widerrufe seine Sitzung sofort, wenn die Wiederherstellung problematisch aussieht.

Nach einem Ergebnis ungleich null sollte der Agent die ursprüngliche Operations-ID beibehalten und zuerst Nur-Lese-Prüfungen ausführen. Er sollte den Bereitstellungsbefehl nicht mit geringfügig verändertem Wortlaut neu erzeugen. Neue Formulierungen erzeugen keinen neuen Zustandsübergang.

Für die obige Bereitstellungsquittung kann ein Prüfungsbefehl sowohl den dauerhaften Datensatz als auch die aktive Nachbedingung untersuchen:

operation_id='release-7f3b'
cat "/var/lib/agent-ops/$operation_id.receipt"
printf 'current='
readlink -f /srv/myapp/current
systemctl is-active myapp
systemctl show myapp --property=ActiveState --property=SubState --no-pager

Die Ausgabe hat eine Form, die der Agent auswerten kann, ohne Prosa als Beweis auszugeben:

operation_id=release-7f3b
release=/srv/myapp/releases/2025-04-18.3
service=myapp
checkpoint=restarted
updated_at=2025-04-18T14:05:12Z
current=/srv/myapp/releases/2025-04-18.3
active
ActiveState=active
SubState=running

Hier zeigt der Symlink auf das angeforderte Release und der Dienst ist aktiv, aber die Quittung endete bei restarted. Der Remote-Prozess kann nach dem Neustart des Dienstes beendet worden sein, bevor er complete schreiben konnte. Das Wiederherstellungsverfahren kann die Nachbedingungen erneut prüfen und bei Erfolg über einen eng begrenzten Wiederherstellungsbefehl eine Abschlussquittung schreiben. Es sollte die Bereitstellung nicht von vorn beginnen.

Ein sinnvolles Controller-Protokoll hält Anfrage, Ergebnis und Wiederherstellung getrennt. Zum Beispiel:

{
  "operation_id": "release-7f3b",
  "action": "deploy_release",
  "target": "app-01",
  "arguments": {
    "release": "/srv/myapp/releases/2025-04-18.3",
    "service": "myapp"
  },
  "mode": "reconcile"
}

Das Ziel sollte mode: reconcile nur für eine Nur-Lese-Prüfung oder einen vorab geschriebenen Abschlussweg akzeptieren, der die Nachbedingung verifiziert. Erlaube dem Agenten nicht, einen beliebigen Shell-String mit der Markierung reconcile zu senden. Diese Bezeichnung hat keine Sicherheitswirkung, wenn der Befehl alles ändern kann.

Der Exit-Code 75 im Beispiel signalisiert absichtlich einen vorübergehenden Fehler. Die konkrete Zahl ist weniger wichtig als ein dokumentierter Vertrag: Der Agent erkennt, dass er den Zustand beobachten und keine automatische Wiederholung senden muss. Verwende für den Controller unterschiedliche Ergebnisse wie completed, reconcile_required, rejected_before_start und transport_unknown. Ein einzelnes boolesches Feld success zerstört die Informationen, die für die Wiederherstellung nötig sind.

Timeouts und Verbindungsabbrüche erfordern Beweise über den Remote-Zustand

Ein Timeout ist eine lokale Beobachtung. Der lokale Client hat aufgehört zu warten, den Remote-Befehl aber nicht unbedingt beendet. Ein Timeout als Abbruch zu behandeln, ist eine der schnellsten Methoden, eine Remote-Aktion doppelt auszuführen.

Ein Controller kann die Unklarheit verringern, indem er Operationen einzeln ausführt. Vor einer Änderung erstellt das Remote-Skript eine exklusive Sperre, die mit seiner Operations-ID verbunden ist. Ein späterer Versuch erkennt die Sperre und entscheidet zwischen Warten, dem Untersuchen des Prozesses oder einer Meldung, dass die Wiederherstellung eine menschliche Entscheidung braucht.

Für eine einfache Sperre auf Host-Ebene reicht flock häufig aus:

exec 9>/var/lib/agent-ops/deploy.lock
if ! flock -n 9; then
  printf 'another deployment operation is active\n' >&2
  exit 75
fi

Das schützt nur Prozesse, die dieselbe Sperre beachten. Es schützt weder vor einem Administrator mit einem separaten Bereitstellungsverfahren noch vor jedem Designfehler in deinen Skripten. Für eine Datenbankmigration solltest du, sofern verfügbar, die Advisory- oder Migrationssperre der Datenbank verwenden. Bei einer API-Aktion solltest du ein von dieser API unterstütztes Idempotenz-Token einsetzen. Die Sperre muss neben dem Zustand liegen, den sie schützt.

Wenn der Controller nach einem Timeout die Verbindung wiederherstellt, prüfe in dieser Reihenfolge:

  1. Lies die Quittung der ursprünglichen Operations-ID.
  2. Prüfe, ob der ursprüngliche Prozess noch läuft, sofern die Operation über eine zuverlässige Prozessmarkierung verfügt.
  3. Teste die Nachbedingung der Operation mit Nur-Lese-Befehlen.
  4. Wähle eine ausdrückliche Wiederherstellungsaktion oder eskaliere, wenn die Beobachtungen widersprüchlich sind.

Verwende die bloße Existenz eines Prozesses nicht als einziges Signal. Ein Prozess kann vorhanden sein und auf eine externe Abhängigkeit warten. Ein fehlender Prozess sagt wenig darüber aus, was er vor seinem Ende geändert hat. Quittung und Nachbedingung liefern stärkere Beweise.

Auch SSH-Multiplexing erfordert Sorgfalt. Eine Master-Verbindung kann den Fehler eines einzelnen Befehls hinter einem gemeinsamen Transport verbergen, und ein Controller kann einen geschlossenen Kanal mit einer fehlgeschlagenen Operation verwechseln. Erfasse stdout, stderr, den unveränderten SSH-Exit-Status sowie Start- und Endzeit des Remote-Befehls als einen Aktionsdatensatz. Bewahre stderr auf, selbst wenn der Agent ihn zusammenfasst. Die Rohdaten zeigen oft, ob Bash eine nicht gesetzte Variable abgelehnt hat, ein Remote-Befehl 75 zurückgab oder SSH selbst 255 lieferte.

Manche Änderungen brauchen Kompensation statt Wiederholungen

Geheimnisse aus Quittungen heraushalten
Sallyport fügt SSH-Zugangsdaten erst zur Ausführungszeit ein und hält Geheimnisse aus dem Agent-Kontext heraus.

Viele Operationen lassen sich nachträglich nicht sicher wiederholbar machen. Die Rotation von Zugangsdaten, destruktive Bereinigung, zahlungsähnliche API-Aufrufe und Schema-Migrationen brauchen ein kompensierendes Verfahren oder eine Entscheidung durch einen Operator.

Nimm einen Ablauf zur Rotation von Zugangsdaten. Der Befehl kann ein neues Zugangstoken erstellen, einen Dienst aktualisieren, den Zugriff prüfen und anschließend das alte Token widerrufen. Scheitert er nach der Erstellung, aber vor der Aktualisierung des Dienstes, kann eine Wiederholung ein weiteres Token erzeugen und mehrere aktive Geheimnisse hinterlassen. Die Quittung sollte die Kennung des neu erstellten Tokens sofort aufzeichnen. Die Wiederherstellung kann dann prüfen, welches Token der Dienst verwendet, und entscheiden, ob das neue aktualisiert, widerrufen oder behalten wird.

Schreibe keine geheimen Werte in die Quittung, in die Standardausgabe oder in den Kontext des Agenten. Speichere nur eine nicht geheime Kennung oder einen Fingerabdruck, sofern diese Kennung selbst keinen Zugriff ermöglicht. Für die Wiederherstellung muss bekannt sein, welches Objekt existiert, nicht sein privater Wert.

Datenbankmigrationen bringen eine andere Falle mit sich. Die Verlaufstabelle eines Migrations-Frameworks kann zeigen, dass eine benannte Migration abgeschlossen wurde. Sie beschreibt jedoch möglicherweise nicht den unterbrochenen Daten-Backfill, der außerhalb der Framework-Transaktion lief. Schreibe Migrationen so, dass Schemaänderung, Backfill-Fortschritt und Abschlussmarkierung getrennt geprüft werden. Unterstützt die Datenbank transaktionales DDL für deine Operation, nutze es. Gehe aber nicht davon aus, dass jede DDL-Anweisung oder externe Nebenwirkung mit der Transaktion zurückgerollt wird.

Bei nach außen sichtbaren Aktionen solltest du ein API-Idempotenz-Token bevorzugen, statt über SSH zu raten. Wenn der Remote-Host eine API aufruft, die einen Idempotenzschlüssel akzeptiert, speichere dieses Token vor der Anfrage in der Quittung. Frage bei der Wiederherstellung die API mit demselben Token ab oder sende die Anfrage entsprechend der dokumentierten Semantik der API mit demselben Token erneut. Für jeden Agent-Versuch ein neues Token zu erzeugen, macht die Funktion wirkungslos.

Die Regel ist eindeutig: Wenn du nicht sagen kannst, wie sich feststellen lässt, ob eine Aktion stattgefunden hat, darfst du einem autonomen Agenten nicht erlauben, sie zu wiederholen. Lass einen Menschen das Ziel prüfen oder gestalte die Operation um einen dauerhaften Zustandsdatensatz herum.

Shell-Skripte brauchen einen Vertrag, den der Agent durchsetzen kann

Ein für Agenten bestimmtes Skript sollte einen engen, maschinenlesbaren Vertrag bereitstellen. Agenten können Zustände nur schlecht aus ausführlichen Logs, farbiger Terminalausgabe und einer Mischung aus Warnungen und Erfolgsmeldungen rekonstruieren.

Verwende stabile Exit-Kategorien, ein Ergebnisobjekt und ausdrückliche Operations-IDs. Schreibe zum Beispiel erst dann eine abschließende JSON-Zeile, wenn der Befehl sein Ergebnis kennt:

{"operation_id":"release-7f3b","outcome":"reconcile_required","checkpoint":"switched","exit_code":75}

Leite gewöhnliche Diagnoseausgaben nach stderr um und reserviere stdout für den Ergebnisdatensatz, sofern dein Controller diese Konvention durchsetzen kann. Ein Shell-Befehl, der Banner, Fortschrittsbalken und JSON gemeinsam nach stdout schreibt, lädt Parserfehler ein. Wenn der Befehl nützlichen Fortschritt ausgibt, schreibe zuerst die dauerhafte Quittung. Der Controller sollte den letzten strukturierten Datensatz als praktische Zusammenfassung behandeln, nicht als einzigen Datensatz.

Lass den Agenten keine beliebigen Checkpoint-Namen, Quittungspfade, Dienstnamen oder Interpreter auswählen. Stelle einen geprüften Befehl mit eingeschränkten Argumenten bereit. Ein Wrapper, der hinter -- beliebige Shell-Befehle akzeptiert, hat das Problem nur hinter eine sauberere Bezeichnung verschoben.

Der Befehlsvertrag sollte außerdem festlegen, welche Fehler sicher wiederholt werden können. Ein Paketdownload kann vor jeder Änderung am Host einen wiederholbaren Netzwerkfehler liefern. Ein nach dem Umschalten des Symlinks unterbrochener Befehl ist nicht wiederholbar, bevor eine Zustandsprüfung das Ziel des Links bestätigt. Diese Klassifizierung gehört zum Autor der Operation, der ihre Auswirkungen kennt, nicht zu einem Modell, das aus stderr rät.

Verwende einen Testhost und unterbrich das Skript an jedem Checkpoint. Sende ein Beendigungssignal während der Archiventpackung, nach dem Umschalten des Symlinks, während des Neustarts und nach dem abschließenden Health-Check. Führe anschließend den Prüfungsweg aus und kontrolliere, ob er die richtige Entscheidung trifft. Wenn du eine Operation noch nie absichtlich unterbrochen hast, weißt du nicht, ob ihr Wiederholungsverhalten sicher ist.

Autorisierung und Audit-Datensätze müssen die Wiederherstellungsgeschichte bewahren

Zugangsdaten nicht an Agenten übergeben
Sallyport bewahrt SSH-Zugangsdaten in einem verschlüsselten Tresor auf und baut die Verbindung selbst auf.

Ein Agent braucht die Berechtigung für eine Wiederherstellungsprüfung. Führt diese Prüfung jedoch zu einer neuen Änderung, muss sie weiterhin der normalen Autorisierungsgrenze folgen. Verstecke keine zweite Bereitstellung in einem Befehl namens status.

Sallyport kann den SSH-Schlüssel außerhalb des Agenten halten und sowohl den Agent-Lauf als auch jede einzelne Aktion aufzeichnen. So bleibt nachvollziehbar, wer einen Wiederherstellungsversuch genehmigt hat und welchen Befehl der Agent gesendet hat. Das Audit-Protokoll ersetzt die Quittung auf dem Zielhost nicht: Der Audit-Datensatz kann beweisen, dass eine Anfrage gestellt wurde, während Quittung und Nachbedingung erklären, welchen Zustand die Aktion auf dem Ziel hinterlassen hat.

Halte diese Datensätze auch in deinen Vorfallsnotizen getrennt. Die Sitzungsidentität beantwortet, welcher Agent-Prozess die Berechtigung hatte. Der einzelne Aktionsdatensatz beantwortet, welcher Befehl gegen welchen Host ausgeführt wurde und was er zurückgab. Die Quittung auf dem Ziel beantwortet, an welcher Stelle die Operation endete. Wenn eine Bereitstellung schiefgeht, verschwendet es die benötigten Beweise, all diese Fakten in einem Chatprotokoll zusammenzufassen.

Verlange eine Bestätigung pro Aufruf bei Operationen mit irreversiblen Auswirkungen, besonders wenn die Wiederherstellung zur Löschung von Zugangsdaten, einer Schema-Reparatur oder einer Bereinigung führen kann. Die zusätzliche Freigabe ist hilfreich, wenn die Beweise des Agenten widersprüchlich sind: etwa wenn die Quittung einen abgeschlossenen Wechsel meldet, der aktive Dienst aber weiterhin ein altes Release anzeigt. Das ist ein Entscheidungspunkt, keine routinemäßige Wiederholung.

Ein gutes Wiederherstellungsdesign macht die vorsichtige Aktion einfach. Gib dem Agenten einen Nur-Lese-Befehl zur Zustandsprüfung, eine dauerhafte Operations-ID und ein definiertes Ergebnis für die Eskalation. Dann erzeugt ein Verbindungsabbruch einen prüfbaren Datensatz statt eines zweiten Versuchs, der das System erneut verändert.

Beginne mit dem Befehl, den alle bereits wiederholen

Finde den SSH-Befehl, den dein Team nach einem Timeout oder einer roten Bereitstellungsmeldung erneut ausführt. Füge eine Operations-ID, eine geschützte Quittung und eine Nur-Lese-Prüfung der Nachbedingung hinzu, bevor du etwas anderes änderst.

Unterbrich ihn anschließend absichtlich. Wenn der Wiederherstellungsweg nicht feststellen kann, ob der erste Versuch den Zustand geändert hat, ist er noch nicht für einen autonomen Agenten geeignet. Überarbeite die Operation so lange, bis die Antwort vom Zielhost kommt und nicht aus dem Vertrauen in einen Exit-Code.

FAQ

Was sagt ein SSH-Exit-Code einem KI-Agenten tatsächlich?

SSH gibt normalerweise den Exit-Status des Remote-Befehls zurück. Das zeigt, ob die Befehlsshell Erfolg gemeldet hat, beweist aber nicht, dass alle vorherigen Aktionen den Host unverändert gelassen haben. Behandle einen Status ungleich null als Signal, den aufgezeichneten Zustand vor jedem erneuten Versuch zu prüfen.

Wann kann ein fehlgeschlagener SSH-Befehl sicher automatisch wiederholt werden?

Automatisch wiederholen solltest du nur dann, wenn jede Operation vor dem Fehler idempotent ist und du ihre Nachbedingung überprüfen kannst. Das Aktualisieren von Paketmetadaten und ein benannter Dienstneustart können dafür geeignet sein. Das Anlegen von Konten, das Ändern von Firewall-Regeln oder Datenmigrationen erfordern normalerweise zuerst eine Zustandsprüfung.

Verhindert set -e teilweise fehlgeschlagene SSH-Befehle?

Nein. set -e beendet eine Shell unter vielen Fehlerbedingungen, hat aber bewusst Ausnahmen bei Bedingungen, Pipelines und Kommando-Substitutionen. Außerdem kann es keine Operation rückgängig machen, die vor dem späteren Fehler bereits abgeschlossen wurde.

Warum sollten Remote-Skripte pipefail verwenden?

Verwende in Bash set -o pipefail, wenn ein Fehler in jedem Teil einer Pipeline das Skript fehlschlagen lassen soll. Ohne diese Option kann curl | tar den Status von tar melden, obwohl curl fehlgeschlagen ist. Für einen Agenten entsteht dadurch ein gefährlich unvollständiges Bild des Ablaufs.

Was sollte eine Quittung für einen SSH-Befehl enthalten?

Speichere eine Laufkennung, die beabsichtigte Operation, jeden abgeschlossenen Checkpoint, den Exit-Status und genügend beobachteten Zustand für die Wiederherstellung. Bewahre die Quittung zusätzlich zum Aktionsprotokoll des Agenten auf dem Remote-Host auf, denn bei einem abgebrochenen SSH-Kanal kann die Ausgabe verloren gehen.

Wie verhindere ich, dass ein Agent dieselbe Änderung zweimal ausführt?

Verwende eine stabile, vom Controller vorgegebene Laufkennung, schreibe Checkpoints atomar in ein Verzeichnis, das nur der Eigentümer verwalten kann, und lasse den Befehl vor seiner Ausführung eine vorhandene Quittung laden. Eine bei jedem Versuch neu erzeugte Zufallskennung kann eine Wiederholung nicht von einer neuen Anfrage unterscheiden.

Was sollte ein Agent nach einem SSH-Timeout tun?

Ein Timeout bedeutet, dass der Controller nicht länger gewartet hat. Es beweist nicht, dass der Remote-Prozess beendet wurde. Prüfe vor einer Wiederholung eine dauerhafte Quittung, den Prozessstatus, eine Sperrdatei, den Dienststatus oder einen Transaktionsdatensatz auf dem Zielhost.

Was bedeutet der SSH-Exit-Code 255?

OpenSSH verwendet den Exit-Status des Remote-Befehls, wenn es ihn empfängt, und gibt für eigene Fehler häufig 255 zurück. Agenten sollten den unveränderten Exit-Code und stderr speichern und dann einen Fehler des Remote-Skripts von einem Transportfehler unterscheiden, bevor sie eine Wiederherstellung wählen.

Wie erkennt ein Agent, ob eine Remote-Änderung bereits erfolgt ist?

Entscheide nicht allein anhand des Exit-Codes. Prüfe die relevante Nachbedingung: Frage den Paketmanager ab, untersuche den Systemdienst, lies die Migrationstabelle oder vergleiche die gewünschte Konfiguration mit der aktiven. Die richtige Wiederherstellungsaktion hängt vom beobachteten Zustand ab.

Sollten SSH-Aktionen von KI-Agenten getrennte Sitzungs- und Befehlsprotokolle haben?

Protokolliere sowohl die Sitzungsidentität als auch jede einzelne SSH-Aktion. Die Sitzungsfreigabe zeigt, welcher Agent-Prozess die Berechtigung erhalten hat. Ein Aktionsdatensatz zeigt, welcher Befehl gegen welches Ziel mit welchem Ergebnis ausgeführt wurde. Wenn diese Informationen vermischt werden, lässt sich ein Vorfall schwerer rekonstruieren.

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