# CLI Feature Probe — DisClaw Manuelles Sanity-Check-Script für kritische Claude Code CLI-Annahmen. Muss vor Phase-2-Freigabe ausgeführt und ausgefüllt werden. Stand: [Datum der letzten Ausführung] CLI-Version: [Ausgabe von `claude --version`] --- ## Kontext Dieses Dokument deckt die vier Annahmen ab, die in `docs/cli-feature-answers.md` als "Unsicherheit: Mittel" oder "empirisch noch nicht bestätigt" markiert sind. Jede Probe muss in einer sauberen Shell-Session ausgeführt werden (keine gespeicherten Umgebungsvariablen aus vorherigen Probes). Voraussetzungen: - `claude` ist auf PATH (oder `CLAUDE_PATH` zeigt auf die CLI) - `jq` ist installiert (`jq --version` gibt etwas aus) - `python3` ist installiert (nur für Probe 3, als Fallback-Pretty-Printer) - Aktiver Claude Pro/Max-Account (die CLI benötigt eine Subscription, keinen separaten API-Key) --- ## Probe 1: `session_id` Top-Level-Position und Stabilität über Resume ### Hypothese Aus `cli-feature-answers.md` Abschnitt 2: > "Die Session-ID liegt als Top-Level-Feld `session_id` im > `--output-format json`-Result. [...] Per Default bleibt die ID beim > `--resume` stabil — die Session wird in der existierenden JSONL > fortgeschrieben." Wir nehmen also an: 1. Der erste `claude -p` Call gibt ein JSON-Objekt zurück, in dem `.session_id` direkt auf Top-Level liegt (nicht unter `.result.session_id` oder ähnlichem). 2. Ein zweiter Call mit `--resume ` gibt dieselbe `session_id` zurück. ### Kommandos ```bash # Schritt 1: Erste Session starten und session_id extrahieren id1=$(claude -p "antworte mit dem wort ALPHA und sonst nichts" \ --output-format json --bare \ | tee /tmp/probe1-run1.json \ | jq -r '.session_id') echo "id1=$id1" # Schritt 2: Session fortsetzen und id aus dem Response lesen id2=$(claude -p "antworte mit dem wort BETA und sonst nichts" \ --resume "$id1" \ --output-format json --bare \ | tee /tmp/probe1-run2.json \ | jq -r '.session_id') echo "id2=$id2" # Schritt 3: Vergleich if [ "$id1" = "$id2" ]; then echo "PASS: IDs sind gleich" else echo "ABWEICHUNG: id1=$id1 id2=$id2" fi # Schritt 4: Komplettes JSON-Top-Level zur Orientierung echo "--- Top-Level-Felder Run 1 ---" jq 'keys' /tmp/probe1-run1.json echo "--- Top-Level-Felder Run 2 ---" jq 'keys' /tmp/probe1-run2.json ``` ### Erwartete Ausgabe ``` id1= id2= ← gleiche UUID wie id1 PASS: IDs sind gleich --- Top-Level-Felder Run 1 --- [ "result", "session_id", "type", ... ] --- Top-Level-Felder Run 2 --- [ "result", "session_id", "type", ... ] ``` `session_id` erscheint direkt in `keys` (Top-Level), nicht nur tief im Baum. Die UUID ist in beiden Runs identisch. ### Tatsächliche Ausgabe ``` [NOCH NICHT AUSGEFÜLLT — Bitte vor Phase-2-Freigabe ausfüllen] ``` ### Status - [ ] Verifiziert - [ ] Abweichung gefunden: ### Notizen Falls `id2 != id1`: Defensiv-Strategie aus `cli-feature-answers.md` Abschnitt 2 ist relevant — nach jedem Run die `session_id` aus dem Response-JSON lesen und in der DB aktualisieren, unabhängig davon ob sie sich geändert hat. Falls `.session_id` nicht auf Top-Level liegt: `jq 'path(.. | strings | select(test("^[0-9a-f-]{36}$")))' /tmp/probe1-run1.json` zeigt den tatsächlichen Pfad. --- ## Probe 2: `--resume` Flow — Kontext-Kontinuität ### Hypothese Aus `cli-feature-answers.md` Abschnitt 1: > "`claude -p "" --resume --output-format json` lädt die > existierende Session und hängt `` als neuen User-Turn an." Wir nehmen an: 1. Claude "erinnert" sich nach einem `--resume` an den Inhalt der vorherigen Turns dieser Session. 2. Der `--resume`-Call schlägt nicht stillschweigend fehl (z. B. mit einer leeren Antwort oder einer neuen, unverbundenen Session), wenn die `session_id` gültig ist. 3. Das Muster `--resume "$session_id" --output-format json --bare` ist ohne weitere Flags ausführbar (kein `--verbose` o. ä. erforderlich). ### Kommandos ```bash # Schritt 1: Session mit einem Geheimnis starten run1=$(claude -p "Merke dir folgendes Codewort für diese Session: XYLOPHON42. Bestätige nur mit OK." \ --output-format json --bare) sid=$(echo "$run1" | jq -r '.session_id') echo "session_id=$sid" echo "Antwort Run 1: $(echo "$run1" | jq -r '.result')" # Schritt 2: In derselben Session das Geheimnis abfragen run2=$(claude -p "Was war das Codewort, das du dir gemerkt hast?" \ --resume "$sid" \ --output-format json --bare) echo "Antwort Run 2: $(echo "$run2" | jq -r '.result')" # Schritt 3: Prüfen ob das Codewort im Response vorkommt if echo "$run2" | jq -r '.result' | grep -q "XYLOPHON42"; then echo "PASS: Codewort in Antwort gefunden" else echo "ABWEICHUNG: Codewort fehlt in der Antwort" fi ``` ### Erwartete Ausgabe ``` session_id= Antwort Run 1: OK Antwort Run 2: Das Codewort war XYLOPHON42. PASS: Codewort in Antwort gefunden ``` ### Tatsächliche Ausgabe ``` [NOCH NICHT AUSGEFÜLLT — Bitte vor Phase-2-Freigabe ausfüllen] ``` ### Status - [ ] Verifiziert - [ ] Abweichung gefunden: ### Notizen Falls das Codewort fehlt: Prüfe ob `--resume` die richtige Session lädt. `ls ~/.claude/projects/` zeigt alle gespeicherten Projekt-Slugs. Das Verzeichnis für den aktuellen cwd hat den Slug ``. Falls `--resume` + `--output-format json` + `--bare` zusammen einen Exit-Code ungleich 0 erzeugen: Flags einzeln testen, um die Inkompatibilität einzugrenzen. --- ## Probe 3: `stream-json` Event-Typen ### Hypothese Aus `cli-feature-answers.md` Abschnitt 4: > "Relevante Typen: `system`, `assistant`, `user`, `stream_event`, `result`." > "Text-Deltas landen unter `.event.delta.type == "text_delta"` mit `.event.delta.text`." > "Für `stream-json` müssen praktisch immer `--verbose` und > `--include-partial-messages` mitgegeben werden, sonst gibt es keine > Text-Deltas, nur den Final-`result`-Block." Wir nehmen an: 1. Mit `--output-format stream-json --verbose --include-partial-messages` erscheinen tatsächlich Zeilen mit `"type":"stream_event"` im Output. 2. Mindestens ein `stream_event` hat `.event.delta.type == "text_delta"` mit einem nicht-leeren `.event.delta.text`. 3. Die letzte (oder eine der letzten) Zeilen hat `"type":"result"`. 4. Ohne `--include-partial-messages` erscheint nur `"type":"result"` (keine `stream_event`-Zeilen). ### Kommandos ```bash # Schritt 1: Stream MIT partial messages — alle Event-Typen anzeigen echo "=== MIT --include-partial-messages ===" claude -p "schreibe genau drei Saetze ueber den Mond" \ --output-format stream-json \ --verbose \ --include-partial-messages \ --bare \ | tee /tmp/probe3-stream.ndjson \ | jq -c '{type, subtype: .subtype?, event_type: .event.type?, delta_type: .event.delta.type?}' # Schritt 2: Welche type-Werte kommen vor? echo "=== Vorkommende type-Werte ===" jq -r '.type' /tmp/probe3-stream.ndjson | sort | uniq -c | sort -rn # Schritt 3: Einen text_delta-Block vollständig anzeigen echo "=== Erster text_delta-Block ===" jq -c 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | {type, text: .event.delta.text}' \ /tmp/probe3-stream.ndjson | head -3 # Schritt 4: Stream OHNE partial messages zum Vergleich echo "=== OHNE --include-partial-messages ===" claude -p "antworte mit einem einzigen Wort: HALLO" \ --output-format stream-json \ --verbose \ --bare \ | jq -c '.type' ``` ### Erwartete Ausgabe ``` === MIT --include-partial-messages === {"type":"system","subtype":"init","event_type":null,"delta_type":null} {"type":"stream_event","subtype":null,"event_type":"content_block_delta","delta_type":"text_delta"} ... (mehrere text_delta-Zeilen) {"type":"result","subtype":null,"event_type":null,"delta_type":null} === Vorkommende type-Werte === 42 stream_event 3 assistant 2 user 1 system 1 result === Erster text_delta-Block === {"type":"stream_event","text":"Der "} {"type":"stream_event","text":"Mond "} {"type":"stream_event","text":"ist "} === OHNE --include-partial-messages === "system" "result" ``` ### Tatsächliche Ausgabe ``` [NOCH NICHT AUSGEFÜLLT — Bitte vor Phase-2-Freigabe ausfüllen] ``` ### Status - [ ] Verifiziert - [ ] Abweichung gefunden: ### Notizen Falls neue, unbekannte `type`-Werte auftauchen: In die Tabelle eintragen. Der DisClaw-Parser in Phase 5 (`src/agent/runner.ts`) muss unknown types silently skippen statt zu crashen. Falls `stream_event` fehlt auch mit `--include-partial-messages`: Prüfe die CLI-Version. Möglicherweise ist der Flag in der installierten Version noch nicht vorhanden. `claude --help | grep partial` prüft das. Falls das Feld-Layout der Text-Deltas abweicht: Den tatsächlichen Pfad mit `jq 'select(.type == "stream_event") | paths' /tmp/probe3-stream.ndjson | head -20` ermitteln und hier dokumentieren. --- ## Probe 4: `usage`-Block Pfade im JSON-Output ### Hypothese Aus `cli-feature-answers.md` Abschnitt 7: > "Der `--output-format json`-Response enthält einen `usage`-Block nach > Anthropic-API-Format mit `cache_read_input_tokens`, `cache_creation_input_tokens`, > `input_tokens`, `output_tokens`." > "Die **genauen JSON-Pfade** sollten aber empirisch geprüft werden — > wahrscheinlich `result.usage.cache_read_input_tokens` oder top-level > `usage.cache_read_input_tokens`." Wir nehmen an: 1. Der `usage`-Block ist im JSON-Output vorhanden (irgendwo im Baum). 2. Er enthält mindestens die Felder `input_tokens` und `output_tokens`. 3. Nach einem `--resume`-Call (zweiter Turn) erscheint `cache_read_input_tokens` mit einem Wert größer 0. ### Kommandos ```bash # Schritt 1: Ersten Run durchführen und usage-Struktur suchen echo "=== usage-Struktur im ersten Run ===" run1=$(claude -p "erklaere in drei saetzen was eine linked list ist" \ --output-format json --bare) echo "$run1" | tee /tmp/probe4-run1.json | python3 -m json.tool # Schritt 2: Alle Pfade finden die "token" enthalten echo "=== Pfade mit 'token' im Namen ===" jq '[path(.. | numbers?) | select(. | map(tostring) | join(".") | test("token"; "i"))]' \ /tmp/probe4-run1.json # Schritt 3: usage direkt ansprechen (alle möglichen Pfade testen) echo "=== usage top-level ===" jq '.usage // "NICHT VORHANDEN"' /tmp/probe4-run1.json echo "=== result.usage ===" jq '.result.usage // "NICHT VORHANDEN"' /tmp/probe4-run1.json # Schritt 4: Zweiten Run mit Resume — cache_read prüfen sid=$(jq -r '.session_id' /tmp/probe4-run1.json) echo "=== usage im Resume-Run (cache_read erwartet) ===" claude -p "fasse deine Erklaerung in einem Satz zusammen" \ --resume "$sid" \ --output-format json --bare \ | tee /tmp/probe4-run2.json \ | jq '.usage // .result.usage // "usage nicht gefunden"' # Schritt 5: cache_read_input_tokens im zweiten Run prüfen echo "=== cache_read_input_tokens in Run 2 ===" jq '(.usage // .result.usage).cache_read_input_tokens // "FELD FEHLT"' \ /tmp/probe4-run2.json ``` ### Erwartete Ausgabe ``` === usage top-level === { "input_tokens": 42, "output_tokens": 87, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0 } === result.usage === "NICHT VORHANDEN" ← oder vice versa, je nach tatsächlichem Pfad === usage im Resume-Run (cache_read erwartet) === { "input_tokens": 12, "output_tokens": 23, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 412 ← größer 0 beim Resume } === cache_read_input_tokens in Run 2 === 412 ``` ### Tatsächliche Ausgabe ``` [NOCH NICHT AUSGEFÜLLT — Bitte vor Phase-2-Freigabe ausfüllen] ``` ### Status - [ ] Verifiziert - [ ] Abweichung gefunden: ### Notizen Falls `cache_read_input_tokens` auch im zweiten Run 0 ist: Das deutet darauf hin, dass Prompt-Caching für kurze Prompts nicht greift (Anthropic cached erst ab ca. 1024 Tokens). Mit einem längeren Prompt testen oder nach mehreren Turns prüfen. Falls der `usage`-Block gar nicht vorhanden ist: Das wäre ein signifikantes Problem für Phase 4/5 (Token-Stats pro Turn). Als Fallback: `null` in der `agent_runs`-Tabelle tolerieren. Tatsächlichen Pfad hier eintragen und in `src/agent/runner.ts` als Konstante definieren, damit er genau einmal im Code steht und leicht geändert werden kann. --- ## Ausfüll-Checkliste | Probe | Ausgeführt von | Datum | Status | |-------|---------------|-------|--------| | 1: session_id Top-Level | | | [ ] | | 2: --resume Flow | | | [ ] | | 3: stream-json Events | | | [ ] | | 4: usage-Block Pfade | | | [ ] | Alle vier Probes müssen den Status "Verifiziert" tragen, bevor Phase 2 (Session-Resume-Implementation) als freigegeben gilt. Abweichungen müssen vor dem Phase-2-Start in `docs/cli-feature-answers.md` nachgezogen werden.