# Kann der Exit-Status einer SSH-Pipeline einen fehlgeschlagenen Befehl verbergen?

Ein entfernter Befehl kann fehlschlagen, ein Formatierer kann überzeugende Ausgabe erzeugen und ein Agent trotzdem Erfolg melden. Das ist kein SSH-Rätsel. Es sind gewöhnliche Regeln der Shell, die eine Netzwerkgrenze überqueren, ohne dass genügend Nachweise mitgegeben werden.

Die Lösung besteht nicht bloß darin, jedem Skript `set -o pipefail` hinzuzufügen. `pipefail` ändert ein zusammengefasstes Ergebnis. Ein Agent, der folgenreiche SSH-Arbeiten ausführt, braucht den Status jeder Pipeline-Stufe, eine festgelegte Regel für erwartete Statuswerte ungleich null und einen endgültigen Exit-Code der entfernten Shell, der nicht mit Erfolg verwechselt werden kann. Erfasse den Vektor sofort, gib ihm einen Namen und lass den Wrapper entscheiden, was Erfolg bedeutet.

## Ein grüner letzter Befehl kann einen roten ersten Befehl verbergen

Standardmäßig meldet eine Shell-Pipeline den Exit-Status ihres letzten Befehls. Das macht diese Zeile bei Bereitstellungen, Migrationen, Backups und Reparaturen gefährlich:

```bash
build_manifest | sign_manifest | tee /var/tmp/manifest.json
```

Nehmen wir an, `build_manifest` schlägt fehl, weil eine benötigte Datei nicht gelesen werden kann. `sign_manifest` erhält dann möglicherweise keine verwertbaren Eingaben und schlägt ebenfalls fehl oder erzeugt ein leeres Ergebnis. `tee` kann trotzdem eine Datei anlegen, null Bytes schreiben und mit Status null enden. Die Shell meldet für die gesamte Pipeline null. Ein Aufrufer, der nur `$?` prüft, sieht Erfolg.

Das GNU Bash Reference Manual beschreibt es eindeutig: Eine Pipeline verwendet den Exit-Status des letzten Befehls, solange `pipefail` nicht aktiviert ist. Bash wartet bei einer synchronen Pipeline auf alle Befehle. Warten bedeutet aber nicht, dass deren Ergebnisse erhalten bleiben.

Ein Mensch an einem interaktiven Terminal bemerkt manchmal die fehlenden Daten oder die Fehlermeldung. Ein Agent hat oft einen engeren Blick. Er erhält möglicherweise nur ein gekürztes Protokoll, eine formatierte Zusammenfassung oder das endgültige Ergebnis des Befehls. Wenn das Skript null meldet, darf der Agent sagen, dass die Aktion erfolgreich war, obwohl die angeforderte Aufgabe nicht erledigt wurde.

Die Unterscheidung, die Teams oft verwischen, ist einfach:

- Der Exit-Status einer Pipeline ist ein einzelner Entscheidungswert.
- Die Statuswerte ihrer Befehle sind die Nachweise hinter diesem Wert.

Du brauchst beides. Der Entscheidungswert bestimmt, ob der entfernte Befehl Erfolg meldet. Die Nachweise zeigen einem Prüfer, einem Protokoll oder einem überwachenden Agenten, wo der Fehler lag.

Das ist besonders wichtig, wenn die erste Stufe die Außenwelt verändert. Stell dir einen entfernten Export vor, der Produktionsdaten liest, sie komprimiert, verschlüsselt und hochlädt. Der Upload-Client kann mit null enden, nachdem er einen leeren Datenstrom hochgeladen hat. Das Protokoll kann beruhigende Wörter wie «abgeschlossen» enthalten, weil ein späteres Programm seine begrenzte Aufgabe erledigt hat. Daraus darf nicht fälschlich werden, dass der Export erfolgreich war.

## SSH gibt zurück, was die entfernte Shell zurückgibt

OpenSSH untersucht die Befehle innerhalb einer Pipeline der entfernten Shell nicht. Es gibt den Status des entfernten Befehls zurück oder 255, wenn SSH selbst auf einen Fehler stößt.

Dieses Verhalten ist richtig und nützlich. SSH kann nicht wissen, ob dieser entfernte Text eine Pipeline, eine Shell-Funktion, ein Skript oder eine Anwendung ist, die Exit-Codes auf ihre eigene Weise verwendet:

```bash
ssh deploy@host 'generate | transform | tee result.txt'
```

Die entfernte Login-Shell interpretiert diesen Befehl. Wenn ihre Pipeline-Regeln den Status von `tee` melden, gibt SSH genau diesen Status an den lokalen Rechner zurück. Nachdem die entfernte Shell die früheren Ergebnisse verworfen hat, kann der lokale Aufrufer sie nicht mehr rekonstruieren.

`set -o pipefail` in der lokalen Shell repariert keine Pipeline, die entfernt läuft. Dieser Befehl ändert nur die Statusregeln der lokalen Pipeline:

```bash
set -o pipefail
ssh deploy@host 'generate | transform | tee result.txt'
```

Die entfernte Shell besitzt weiterhin `generate | transform | tee result.txt`. Sie braucht ihre eigene explizite Shell und eine eigene Fehlerbehandlung.

Es gibt noch eine zweite Falle. Dieser lokale Befehl erzeugt nach der Rückkehr von SSH eine weitere Pipeline:

```bash
ssh deploy@host 'remote command' 2>&1 | tee session.log
```

Jetzt existieren zwei verschiedene Pipelines:

1. Die entfernte Shell kann innerhalb von `remote command` eine Pipeline enthalten.
2. Die lokale Shell hat `ssh | tee session.log`.

Ein erfolgreiches lokales `tee` kann einen Transportfehler von SSH oder einen Status ungleich null des entfernten Wrappers verbergen. Du musst die entfernte Pipeline auf dem Host und die lokale Pipeline um SSH herum prüfen. Wenn du die Zeile als einen undurchsichtigen Befehl behandelst, können fälschlich grüne Ergebnisse eine Prüfung überstehen.

## Pipefail erkennt einen Fehler, erklärt ihn aber nicht

`set -o pipefail` ändert das zusammengefasste Ergebnis von Bash. Wenn die Option aktiviert ist, gibt Bash den Status des am weitesten rechts stehenden Befehls zurück, der mit einem Status ungleich null beendet wurde. Wenn alle Befehle erfolgreich waren, ist das Ergebnis null.

Für viele Skripte ist das eine echte Verbesserung:

```bash
set -o pipefail
produce_data | validate_data | publish_data
printf 'pipeline status: %s\n' "$?"
```

Wenn `produce_data` mit 17 endet und die späteren Befehle mit null, gibt die Pipeline 17 zurück. Wenn `validate_data` mit 4 endet und `publish_data` mit null, gibt die Pipeline 4 zurück. Der aufrufende Prozess erhält einen Fehler statt einer Lüge.

`pipefail` verliert jedoch Details, wenn mehrere Stufen fehlschlagen. Angenommen, die Statuswerte lauten `17 4 0`. Das Pipeline-Ergebnis ist 4, weil 4 vom am weitesten rechts stehenden fehlgeschlagenen Befehl stammt. Du weißt damit, dass ein Fehler aufgetreten ist. Es ist aber nicht klar, ob der Validator den Fehler des Produzenten verursacht, darauf reagiert oder unabhängig davon fehlschlägt.

Darum ist `pipefail` eine Schutzmaßnahme, kein Berichtsformat. Verwende es, wenn eine Pipeline als Einheit fehlschlagen soll. Verwende `PIPESTATUS`, wenn du später diese Fragen beantworten musst:

- Welche Stufe gab einen Status ungleich null zurück?
- Wurde eine spätere Stufe ausgeführt und war erfolgreich, nachdem eine frühere Stufe fehlgeschlagen war?
- Hat der Prozess ein Signal empfangen, statt seinen eigenen Fehler zurückzugeben?
- Ist ein Status ungleich null für diesen bestimmten Befehl erwartet?

Übertünche die Lücke nicht mit `|| true`:

```bash
produce_data | validate_data | publish_data || true
```

Dieses Muster ist beliebt, weil ein Skript dadurch weiterläuft. Gleichzeitig löscht es das einzige Signal, das der Aufrufer hatte. Wenn eine Stufe rechtmäßig mit einem Status ungleich null enden darf, hinterlege den erlaubten Status für diese Stufe, nachdem du den tatsächlichen Vektor erfasst hast. Hebe nicht den Fehler der gesamten Pipeline auf.

## PIPESTATUS verschwindet, sobald du auch nur einen Befehl abwartest

Bash stellt den Exit-Code jeder Stufe im Array `PIPESTATUS` bereit. Das Array ist absichtlich empfindlich: Es beschreibt die zuletzt ausgeführte Pipeline im Vordergrund, und der nächste Befehl kann es ersetzen.

Das sieht vernünftig aus, ist aber falsch:

```bash
source_data | normalize | upload
pipeline_rc=$?
printf 'pipeline result: %s\n' "$pipeline_rc"
statuses=("${PIPESTATUS[@]}")
```

Wenn Bash die letzte Zuweisung erreicht, wurden `pipeline_rc=$?` und `printf` bereits ausgeführt. `PIPESTATUS` beschreibt dann nicht mehr `source_data | normalize | upload`.

Kopiere das Array zuerst, bevor du irgendetwas anderes tust:

```bash
source_data | normalize | upload
statuses=("${PIPESTATUS[@]}")
```

Untersuche es anschließend, ohne dich auf den zusammengefassten Pipeline-Code zu verlassen:

```bash
printf 'source_data=%s normalize=%s upload=%s\n' \
  "${statuses[0]}" "${statuses[1]}" "${statuses[2]}"
```

Auch deshalb kann ein beiläufig eingesetztes `set -e` die Diagnose verschlechtern. Bei aktivem `pipefail` kann eine fehlgeschlagene Pipeline dazu führen, dass Bash beendet wird, bevor die nächste Zeile `PIPESTATUS` kopiert. Die Fehlerbehandlung der Shell hat mehrere kontextabhängige Ausnahmen. Skripte, die sich allein auf `set -e` verlassen, liefern daher gerade beim Fehlschlag eines Befehls oft weniger Nachweise.

Wenn die Statuswerte einer Pipeline wichtig sind, schalte `errexit` für die wenigen Zeilen aus, die sie ausführen und sichern. Triff danach eine explizite Entscheidung. Das ist mehr Code als eine magische Shell-Option, aber du kannst ihn während eines Vorfalls lesen.

## Führe das entfernte Programm unter der benötigten Shell aus

`PIPESTATUS` ist ein Bash-Array. Es ist keine portable POSIX-`sh`-Syntax, und `pipefail` ist in der POSIX-Shell nicht vorgeschrieben. Ein über SSH aufgerufener entfernter Befehl kann unter einer Login-Shell laufen, die du nicht ausgewählt hast. Auf einem Host ist es vielleicht Bash, auf einem anderen `dash`, `zsh` oder eine eingeschränkte Shell.

Sende keine Bash-Syntax an eine nicht näher bestimmte entfernte Shell und hoffe, dass der Rechner zufällig dieselbe Annahme macht wie du. Starte Bash ausdrücklich:

```bash
ssh deploy@host 'bash -s' <<'REMOTE_SCRIPT'
printf 'alpha\n' | grep 'beta' | tee /var/tmp/example.out
statuses=("${PIPESTATUS[@]}")
printf 'stages=%s,%s,%s\n' \
  "${statuses[0]}" "${statuses[1]}" "${statuses[2]}" >&2
REMOTE_SCRIPT
```

Der quotierte Heredoc-Begrenzer ist wichtig. `<<'REMOTE_SCRIPT'` verhindert, dass die lokale Shell Variablen, Befehlsersetzungen und Backslashes erweitert, bevor sie das Skript sendet. Der entfernte Bash-Prozess erhält genau den geschriebenen Text.

Auf macOS ist die Systemversion von Bash alt, unterstützt aber indizierte Arrays, `PIPESTATUS` und `set -o pipefail`. Das bedeutet nicht, dass `/bin/sh` Bash ist. Ein Skript mit `#!/bin/bash` hilft nur, wenn du die Datei direkt ausführst. Wenn du eine einzeilige Anweisung wie `ssh host '...'` übergibst, interpretiert die entfernte Login-Shell sie weiterhin, sofern du nicht ausdrücklich Bash startest.

Für einen dauerhaft gepflegten Automatisierungsweg legst du den entfernten Wrapper in einem versionierten Skript ab und rufst seinen absoluten Pfad auf. Für kurzfristige Agentenarbeit ist `bash -s` mit einem quotierten Heredoc meist leichter zu prüfen, weil das vollständige entfernte Programm in der lokalen Aktionsanforderung sichtbar ist.

## Ein Wrapper sollte Stufen benennen und ein ehrliches Ergebnis zurückgeben

Ein nützlicher entfernter Wrapper erledigt vier Aufgaben. Er führt die Pipeline aus, kopiert den Statusvektor sofort, gibt einen maschinenlesbaren Datensatz aus und endet mit einem Status ungleich null, wenn eine erforderliche Stufe fehlgeschlagen ist.

Dieses Beispiel verwendet eine Datenübertragung mit drei Stufen. Ersetze die Befehle, aber behalte den Kontrollfluss bei. Es verlässt sich bewusst nicht auf `set -e`, um zu entscheiden, was nach der Pipeline geschieht.

```bash
#!/usr/bin/env bash
set -uo pipefail

run_export() {
  local -a status
  local stage
  local -a names=("collect" "compress" "send")

  set +e
  collect_records | gzip -c | send_archive --destination daily
  status=("${PIPESTATUS[@]}")
  set -e

  if ((${#status[@]} != ${#names[@]})); then
    printf 'agent_pipeline_error pipeline=export reason=status_count expected=%s got=%s\n' \
      "${#names[@]}" "${#status[@]}" >&2
    return 70
  fi

  for stage in "${!names[@]}"; do
    printf 'agent_pipeline_status pipeline=export stage=%s code=%s\n' \
      "${names[$stage]}" "${status[$stage]}" >&2
  done

  for stage in "${!status[@]}"; do
    if (( status[stage] != 0 )); then
      printf 'agent_pipeline_result pipeline=export outcome=failed\n' >&2
      return "${status[$stage]}"
    fi
  done

  printf 'agent_pipeline_result pipeline=export outcome=ok\n' >&2
  return 0
}

run_export
```

Eine fehlgeschlagene Sammlung mit erfolgreicher Komprimierung und erfolgreichem Versand erzeugt eine Ausgabe dieser Form:

```text
agent_pipeline_status pipeline=export stage=collect code=23
agent_pipeline_status pipeline=export stage=compress code=0
agent_pipeline_status pipeline=export stage=send code=0
agent_pipeline_result pipeline=export outcome=failed
```

Der Wrapper endet mit 23. SSH gibt 23 an den lokalen Prozess zurück. Der Agent kann melden, dass der Export bei `collect` fehlgeschlagen ist, selbst wenn `send_archive` für einen leeren Datenstrom eine Abschlussmeldung ausgegeben hat.

Der genaue zurückgegebene Code ist weniger wichtig als die dahinterstehende Disziplin. In diesem Wrapper gewinnt der erste Status ungleich null in Pipeline-Reihenfolge. Bash `pipefail` wählt stattdessen den am weitesten rechts stehenden Status ungleich null. Beide Regeln können funktionieren, wenn du eine davon festlegst und testest. Für Betriebsaufgaben bevorzuge ich die erste fehlgeschlagene Stufe, weil sie meist näher am auslösenden Fehler liegt. Bewahre den vollständigen Statusvektor im Aktionsdatensatz auf, damit niemand aus einer einzelnen Zahl erschließen muss, was passiert ist.

Die Namen der Stufen sind keine Dekoration. `0=23,1=0,2=0` zwingt eine Person, das Skript erneut zu öffnen. `collect=23,compress=0,send=0` ermöglicht es einer überwachenden Instanz, den Fehler weiterzuleiten, Kontext hinzuzufügen oder zu entscheiden, ob ein erneuter Versuch sicher ist.

## Lokales Protokollieren kann einen zweiten falschen Erfolg erzeugen

Operatoren wollen ein lokales Protokoll. Agenten brauchen es ebenfalls. Der naive Weg sieht so aus:

```bash
ssh deploy@host 'bash -s' < remote-export.sh 2>&1 | tee ssh-export.log
```

Wenn SSH 23 zurückgibt, das lokale `tee` aber das Protokoll schreibt und mit null endet, gibt die lokale Pipeline standardmäßig null zurück. Du hast die entfernte Lüge behoben und eine lokale eingeführt. Erfasse auch die lokalen Statuswerte:

```bash
set +e
ssh deploy@host 'bash -s' < remote-export.sh 2>&1 | tee ssh-export.log
local_status=("${PIPESTATUS[@]}")
set -e

ssh_rc=${local_status[0]}
tee_rc=${local_status[1]}
printf 'ssh=%s tee=%s\n' "$ssh_rc" "$tee_rc" >&2

if (( ssh_rc != 0 )); then
  exit "$ssh_rc"
fi
if (( tee_rc != 0 )); then
  exit "$tee_rc"
fi
```

Aktiviere nicht einfach die lokale Option `pipefail` und belasse es dabei. Sie liefert ein Ergebnis ungleich null, wenn `ssh` oder `tee` fehlschlägt, und ist damit besser als das Standardverhalten. Sie kann dem Agenten aber nicht sagen, ob die entfernte Aktion, die Netzwerkverbindung oder die lokale Protokollierung fehlgeschlagen ist. Diese Fälle führen zu unterschiedlichen Entscheidungen.

Der SSH-Status 255 braucht eine besondere Behandlung. OpenSSH reserviert ihn für einen Fehler im SSH-Clientpfad, nicht für das Ergebnis eines entfernten Befehls. Ein Wrapper sollte ihn als Transport- oder SSH-Ausführungsfehler melden, nicht behaupten, dass eine benannte entfernte Pipeline-Stufe 255 zurückgegeben hat.

Es gibt noch einen praktischen Grund, lokale und entfernte Ergebnisse getrennt zu halten. Ein Protokoll kann mehrere entfernte Pipeline-Datensätze, Warnungen der Login-Shell und eine SSH-Diagnose enthalten. Wenn ein Agent die letzte Zahl im freien Text auswertet, wird er irgendwann die falsche Zahl auswählen. Verwende erkennbare Datensätze und verknüpfe das endgültige Aktionsergebnis mit dem tatsächlichen Prozess-Exit-Status.

## SIGPIPE braucht eine dokumentierte Ausnahme, keine pauschale Entschuldigung

`pipefail` macht einen Fehler sichtbar, den viele Skripte zuvor ignoriert haben: SIGPIPE. In Bash erhält ein durch Signal Nummer `N` beendeter Prozess den Status `128 + N`; SIGPIPE erscheint häufig als 141.

Ein klassischer absichtlicher Fall ist:

```bash
generate_many_lines | head -n 10
```

`head` liest zehn Zeilen und endet erfolgreich. Der Generator schreibt möglicherweise weiter, empfängt SIGPIPE, weil kein Leser mehr vorhanden ist, und endet mit 141. Mit `pipefail` kann die Pipeline fehlgeschlagen aussehen, obwohl die angeforderten zehn Zeilen erzeugt wurden.

Das macht 141 nicht in jeder Pipeline harmlos. Ein Netzwerk-Client, Kompressor oder Datenproduzent kann SIGPIPE erhalten, weil ein unerwartet beendeter nachgeschalteter Verbraucher abgestürzt ist oder Eingaben abgelehnt hat. Wenn du jeden Status 141 als Erfolg markierst, verbirgst du eine fehlerhafte Übertragung.

Die richtige Regel ist eng gefasst: Erlaube einen von einem Signal abgeleiteten Status nur bei einer Stufe, deren vorzeitiges Ende Teil des vorgesehenen Vertrags des Befehls ist. Platziere diese Ausnahme neben der Stufe, nicht in einer globalen Shell-Einstellung.

Ein Wrapper für eine absichtliche Vorschau kann beispielsweise `generate_many_lines=141` nur dann akzeptieren, wenn `head=0` gilt:

```bash
if (( status[0] == 141 && status[1] == 0 )); then
  printf 'agent_pipeline_result pipeline=preview outcome=ok reason=expected_sigpipe\n' >&2
  return 0
fi
```

Jedes andere Ergebnis ungleich null bleibt ein Fehler. Diese kleine Genauigkeit verhindert eine häufige Überreaktion: Menschen aktivieren `pipefail`, sehen einmal ein störendes 141 und deaktivieren die Option anschließend in der gesamten Automatisierungsumgebung.

## Ein Agent braucht Nachweise getrennt von der Befehlsausgabe

Der Agent sollte den Erfolg nicht durch das Lesen von Prosa bestimmen. Befehle geben Erfolgswörter aus, bevor sie fehlschlagen, Werkzeuge mischen Warnungen mit Ergebnissen und ein entferntes Skript kann eine letzte Zeile ausgeben, obwohl eine Stufe bereits schiefgelaufen ist.

Definiere einen Aktionsvertrag mit zwei Ebenen:

1. Der Prozess-Exit-Code entscheidet, ob die angeforderte Aktion erfolgreich war.
2. Strukturierte Statusdatensätze erklären jede relevante Pipeline-Stufe.

Halte die normale Befehlsausgabe zur Fehlersuche verfügbar, aber verlange vom Agenten nicht, daraus den Kontrollfluss abzuleiten. Im obigen Wrapper stehen die Datensätze in stderr und beginnen mit `agent_pipeline_status` oder `agent_pipeline_result`. Ein aufrufendes Programm kann diesen Datenstrom bewahren, nur diese exakten Datensätze auswerten und den übrigen Inhalt trotzdem einem Menschen anzeigen.

Vertraue einer Markierung nicht allein deshalb, weil sie in der nicht vertrauenswürdigen Befehlsausgabe erscheint. Wenn eine Pipeline-Stufe Daten verarbeitet, die von einem anderen Benutzer oder System stammen, können diese Daten eine Zeile enthalten, die deinem Statusdatensatz ähnelt. Sicherer ist es, wenn der Wrapper die Ausgabe der Stufen erfasst und die Datensätze selbst nach Abschluss der Pipeline ausgibt. Bei risikoreicheren Aufgaben kannst du eine eigene Ergebnisdatei mit restriktiven Berechtigungen verwenden. Der Wrapper liest und validiert sie dann, bevor er einen abschließenden Datensatz ausgibt.

Der Bericht des Agenten sollte den Exit-Code der entfernten Aktion, den lokalen SSH-Exit-Code und, sofern verfügbar, die benannten Statuswerte der entfernten Stufen enthalten. Außerdem sollte er zwischen diesen Ergebnissen unterscheiden:

- Die entfernte Aktion wurde ausgeführt und eine benannte Stufe ist fehlgeschlagen.
- Der entfernte Wrapper konnte keinen vollständigen Statusdatensatz erzeugen.
- SSH konnte den Aktionskanal nicht aufbauen oder aufrechterhalten.
- Die lokale Protokollerfassung ist fehlgeschlagen, nachdem die entfernte Aktion abgeschlossen war.

Das sind betrieblich unterschiedliche Tatsachen. Ein erneuter Versuch nach einem Netzwerkausfall kann eine bereits abgeschlossene Änderung auf dem entfernten System wiederholen. Ein erneuter Versuch nach einer fehlgeschlagenen Validierungsstufe kann sicher sein. Ein erneuter Versuch nach einem fehlgeschlagenen lokalen `tee` ist möglicherweise sinnlos, weil die entfernte Arbeit bereits erledigt wurde.

Sallyport kann die SSH-Zugangsdaten vom Agenten fernhalten, während es die SSH-Aktion ausführt. Der entfernte Befehl braucht trotzdem diesen ehrlichen Vertrag für Exit-Status und Nachweise.

## Teste die Fehlerpfade, bevor ein Agent sie erreicht

Ein Shell-Wrapper verdient Vertrauen erst, nachdem er auf kontrollierte Weise fehlschlägt. Der erfolgreiche Standardpfad beweist den am wenigsten interessanten Zweig.

Erzeuge wegwerfbare Befehle, die die gewünschten Statuswerte zurückgeben:

```bash
fail_23() { printf 'collector failed\n' >&2; return 23; }
pass_through() { cat; }
succeed() { cat >/dev/null; return 0; }

set +e
fail_23 | pass_through | succeed
status=("${PIPESTATUS[@]}")
set -e
printf 'observed=%s,%s,%s\n' "${status[0]}" "${status[1]}" "${status[2]}"
```

Das erwartete Ergebnis ist `23,0,0`. Führe dasselbe Muster anschließend über genau den SSH-Aufruf aus, den dein Agent verwendet. Ein lokaler Shell-Test reicht nicht, weil die Auswahl der entfernten Shell, die Heredoc-Quotierung, die lokale Protokollpipeline und das Exit-Verhalten des Wrappers außerhalb dieser ersten Prüfung liegen.

Teste mindestens diese Fälle:

- Jede Stufe ist erfolgreich und der Wrapper endet mit null.
- Eine frühe Stufe schlägt fehl, während spätere Stufen mit null enden.
- Eine mittlere Stufe schlägt fehl, nachdem sie Eingaben verarbeitet hat.
- SSH kann keine Verbindung herstellen oder sich nicht authentifizieren.
- Das lokale `tee` kann sein Protokoll nicht schreiben.
- Eine absichtliche `head`-Pipeline löst die erwartete SIGPIPE-Regel aus.

Notiere für jeden Fall den erwarteten Exit-Code und die erwarteten Statusdatensätze der Stufen. Wenn ein Test Erfolg meldet, obwohl eine frühere Stufe mit einem Status ungleich null endet, erfüllt der Wrapper seine Aufgabe nicht.

Die verlockende Abkürzung besteht darin, den Agenten nach jeder Aktion ein Protokoll prüfen und beurteilen zu lassen, ob die Ausgabe «richtig aussieht». Das versagt unter Last, wenn Werkzeuge ihre Formulierungen ändern oder die Ausgabe gekürzt wird. Exit-Codes sind der Steuerkanal. Statusdatensätze der Stufen sind der Nachweiskanal. Halte beide getrennt, bewahre beide über SSH hinweg auf und lass nicht `tee` am Ende entscheiden, ob eine entfernte Aktion stattgefunden hat.
