disclaw/docs/cli-feature-probe.md
Nick Tabeling e3ce0857be
Some checks failed
CI / build-and-test (ubuntu-latest) (pull_request) Has been cancelled
CI / build-and-test (windows-latest) (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
docs(DIS-110): add cli-feature-probe.md for Phase 2 sanity checks
Manual probe script covering session_id field path, --resume flow,
stream-json event types, and usage block field names.
Must be executed and filled in before Phase 2 release.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 17:08:52 +02:00

12 KiB

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 <id> gibt dieselbe session_id zurück.

Kommandos

# 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=<uuid>
id2=<uuid>          ← 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 "<neue-msg>" --resume <session-id> --output-format json lädt die existierende Session und hängt <neue-msg> 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

# 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=<uuid>
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 <cwd-Pfad-mit-Bindestrichen>.

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

# 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

# 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.