Zum Inhalt springen

Ausgabe als JSON und Exit-Codes

JSON ohne Hülle auf der Standardausgabe, Fehler nur auf der Fehlerausgabe, --raw bei Vertragsbrüchen und die 13 Exit-Codes von 0 bis 12.

4 Minuten LesezeitCLIStand: 25.10.2026

Auf dieser Seite

Bevor Sie loslegen

Für wen
Wer die CLI aus Skripten aufruft und Ergebnis, Fehler und Wiederholung auseinanderhalten muss
Voraussetzung
die CLI direkt über node …/dist/main.js oder als szenera aufgerufen — nie über pnpm run, das jeden Fehlschlag als 1 meldet

Was die CLI auf welche Ausgabe schreibt und welcher Exit-Code welche Fehlerart bedeutet — die Tabelle, gegen die ein Skript entscheidet, ohne Text zu vergleichen.

Die Form der Ausgabe

Situation Was die CLI tut
Standardausgabe ist kein Terminal (Röhre, Datei) JSON, ohne dass --json nötig wäre
Standardausgabe ist ein Terminal Kopfzeile und Tabelle für Menschen
--json bzw. --table erzwingt die Form; schlägt die Umgebungsvariable
SZENERA_OUTPUT=json bzw. table erzwingt die Form; schlägt die Erkennung

Im JSON-Modus steht auf der Standardausgabe die Nutzlast und sonst nichts — keine Hülle, kein ok, kein dauerMs. Eine Liste ist deshalb .items[], nicht .daten.items[]; nextCursor steht, solange weitere Seiten folgen, direkt in der Antwort:

szenera scene list --json | jq -r '.items[].id'
szenera device list > geraete.json

Für Menschen gilt das Gegenteil: Die Kopfzeile steht immer zuerst — auch bei einem schreibenden Befehl ohne Tabelle —, darunter die Tabelle oder, wenn der Befehl keine hat, die Daten als JSON.

Fehler gehen auf die Fehlerausgabe

Fehler landen immer auf der Fehlerausgabe, nie auf der Standardausgabe. szenera device list > geraete.json hinterlässt im Fehlerfall keine Datei, die aussieht wie ein Ergebnis. Im JSON-Modus trägt der Fehler seine Art als Wort, damit ein Protokoll später ohne diese Tabelle lesbar bleibt:

{
  "error": {
    "kind": "forbidden",
    "message": "…",
    "exitCode": 4,
    "code": "Permission.Denied",
    "status": 403,
    "traceId": "trc_…"
  }
}

code, status, traceId, details und retryAfterSeconds stehen dabei, wenn der Fehler von der API kam. Im Tabellen-Modus lautet dieselbe Meldung ✗ …, gefolgt von HTTP 403 · Permission.Denied und Spur trc_…. Fortschrittszeilen wartender Befehle gehen ebenfalls auf die Fehlerausgabe; die Standardausgabe trägt am Ende genau ein Dokument.

Die Exit-Codes

Code Art Woher
0 ok alles gut
1 usage unbekannter Befehl, fehlende Stellung, unbekannte Flagge, Flagge ohne Wert, fehlendes --yes, falscher Kennungs-Präfix — und eine Anfrage, die dem Vertrag widerspricht (mit Feldpfad)
2 config Profil fehlt oder ist kaputt, kein Schlüssel-Präfix, Profil gesperrt (readOnly: true)
3 auth HTTP 401
4 forbidden HTTP 403
5 not-found HTTP 404
6 conflict HTTP 409 — Zustands- oder Fassungskonflikt
7 validation HTTP 422 — der Server hat die Eingabe abgelehnt
8 rate-limited HTTP 429; retryAfterSeconds steht im Fehler
9 server HTTP 5xx oder eine Antwort, die dem Vertrag widerspricht
10 network gar keine HTTP-Antwort: kein Netz, kein Name, abgebrochen
11 failed der Vorgang kam durch und endete rot (Verarbeitung gescheitert, Upload abgewiesen, Befehl failed)
12 timeout das Zeitlimit war erreicht; der Ausgang ist unbekannt

Codes 1 und 2 sind Fehler auf Ihrer Seite, 3 bis 8 Absagen des Servers mit klarer Ursache, 9 und 10 Ursachen, bei denen ein späterer Versuch Sinn ergibt, 11 und 12 die Ausgänge eines Vorgangs, der lange nach seinem Aufruf endet. Andere Statuscodes unter 500, die keiner Zeile entsprechen, enden als 1.

11 und 12 sind nicht 9

Beide kamen durch: Der Aufruf war richtig, der Server hat geantwortet. Bei 11 ist das Ergebnis negativ — asset upload konnte die Bytes schieben, die Verarbeitung ist gescheitert; der Befund steht im Fehler. Bei 12 ist das Ergebnis offen — der Vorgang läuft womöglich weiter. Wer 12 wie „gescheitert“ behandelt, stößt einen laufenden Vorgang ein zweites Mal an; wer 11 wie 9 behandelt, wiederholt einen Aufruf, dessen Antwort schon feststeht.

Anfrage und Antwort sind zwei Seiten

Der erzeugte Client prüft beide Richtungen gegen dieselben Schemas. Ging für den Aufruf kein Netzpaket hinaus, war es die Anfrage — Exit 1, mit Feldpfad:

$ szenera scene update scn_01K… --document=kaputt.json --version=2
{ "error": { "kind": "usage", "exitCode": 1, "message":
  "Die Anfrage an „composition.scenes.update" entspricht nicht dem Vertrag:
     · draftDocument.canvas: Required
     · draftDocument.widgets: Expected array, received string" } }

Bricht die Antwort den Vertrag, ist der häufige Grund ein Versatz der Stände — die Gegenstelle ist älter oder neuer als die CLI und schickt ein Feld nicht, das diese schon verlangt. Die Meldung sagt das, nennt die Stellen und den Weg zur Rohantwort:

$ szenera device get dev_01K… --raw
✗ Die Antwort auf „fleet.devices.get" widerspricht dem Vertrag, den diese CLI (0.1.0) kennt.
    Wahrscheinlich fährt die Gegenstelle einen anderen Stand — nicht: dort ist etwas kaputt.
    · displays: Required
    Was tatsächlich ankam, zeigt „--raw".

--raw gilt überall und legt bei genau diesem Fehler die Rohantwort auf die Standardausgabe (null, wenn kein Rumpf ankam); die Diagnose bleibt auf der Fehlerausgabe, der Exit-Code bleibt 9. Es ist die einzige Stelle, an der ein gescheiterter Aufruf etwas auf die Standardausgabe schreibt — damit das Dokument durch jq gehen kann statt aus einer Fehlermeldung gefischt zu werden.

Weiterführendes