disclaw/docs/cli-feature-answers.md
Nick Tabeling 69e0b7d727 initial
2026-04-08 14:21:19 +02:00

27 KiB

Antworten AI Engineer — CLI Feasibility

Stand: 2026-04-08. Verifiziert gegen die offizielle Anthropic-Dokumentation auf code.claude.com/docs/en/* (ehemals docs.claude.com/en/docs/claude-code/*, 301-Redirect). Für DisClaw relevante Claude-Code-CLI-Version: aktuelle stable (>= 2.1.59 wegen Auto-Memory).


1. -p + --resume

Antwort: -p und --resume sind kombinierbar. Die Doku zeigt das Muster explizit:

session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"

Das heißt: 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. --continue / -c ist die "letzte Session in diesem cwd"-Variante und akzeptiert ebenfalls -p (z. B. claude -c -p "query"). Unterschied: --resume adressiert eine konkrete Session per ID oder Name, --continue nimmt implizit die zuletzt benutzte Session im aktuellen Arbeitsverzeichnis.

Zusatz: Ab aktueller CLI gibt es --fork-session, das beim Resumen eine neue Session-ID erzeugt statt die alte weiterzuschreiben. Für DisClaw irrelevant, solange wir Kontinuität wollen, aber relevant falls wir Branching-Experimente machen.

Quelle / Konfidenz: Hoch. code.claude.com/docs/en/headless Abschnitt "Continue conversations" und code.claude.com/docs/en/cli-reference (Flag-Tabelle --resume, -r, --continue, -c, --fork-session).

Implikation für DisClaw: Phase 2 (Session-Resume) kann wie geplant umgesetzt werden. Wir speichern die session_id pro Channel und rufen bei jeder neuen Nachricht claude -p <msg> --resume <id> --output-format json auf. --continue ist Fallback, wenn die persistierte ID ungültig wurde (sessionweit oder nach cleanupPeriodDays).


2. session_id im JSON-Output

Antwort: Die Session-ID liegt als Top-Level-Feld session_id im --output-format json-Result. Das belegt das offizielle Doku-Snippet jq -r '.session_id' (siehe Frage 1). Das gleiche Feld erscheint auch in den stream-json-Events (dort pro Event als session_id).

Zur Stabilität der ID über mehrere Turns: Per Default bleibt die ID beim --resume stabil — die Session wird in der existierenden JSONL fortgeschrieben. Eine neue ID wird nur erzeugt, wenn man explizit --fork-session setzt, oder wenn man --session-id <uuid> verwendet, um eine eigene UUID zu forcieren. Unsicherheit: In einigen älteren CLI-Versionen wurde beim Resume eine neue Session-Datei angelegt. Mit der aktuellen stable sollte das nicht mehr sein, aber wir sollten defensive programmieren und die aus dem JSON-Output gelesene session_id nach jedem Run aktualisieren in der DB (billig, ein UPDATE pro Turn).

Quelle / Konfidenz: Mittel-hoch. Doku bestätigt das Feld explizit; Verhalten "ID bleibt stabil" ist aus dem Doku-Beispiel abgeleitet, aber nicht wortwörtlich zugesichert.

Probe-Command (zur Verifikation):

id1=$(claude -p "sag hallo" --output-format json --bare | jq -r '.session_id')
id2=$(claude -p "und jetzt tschuess" --resume "$id1" --output-format json --bare | jq -r '.session_id')
echo "id1=$id1 id2=$id2"
# erwartet: id1 == id2

Implikation für DisClaw: Wir persistieren die session_id pro channel_id und aktualisieren sie nach jedem Response-Parse. Falls id2 != id1 auftritt (alte CLI, Fork-Verhalten, Cleanup), fängt das UPDATE das transparent auf. Bei session_id-null (Fehler) behalten wir die alte ID und versuchen nächstes Mal --continue als Fallback.


3. .claude/settings.json Permissions-Schema

Antwort: Aktuelles Schema ist klar:

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": ["Bash(git diff *)", "Read"],
    "ask":   ["Bash(git push *)"],
    "deny":  ["WebFetch", "Bash(curl *)", "Read(./.env)"],
    "defaultMode": "acceptEdits",
    "additionalDirectories": ["../docs/"]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Wichtig: permissions.allow / permissions.ask / permissions.deny ist das settings-file-Schema. --allowedTools / --disallowedTools sind die CLI-Flags mit gleicher Regel-Syntax — intern landen sie auf derselben Permission-Engine. Beide existieren parallel, keines ist deprecated. Für DisClaw (persistente Agent-Identität) ist permissions.* im settings.json das richtige; CLI-Flags nutzen wir zusätzlich nur für Run-spezifische Overrides.

Hook-Struktur ist dreistufig verschachtelt: hooks.<EventName>[].matcher + hooks.<EventName>[].hooks[].{type, command, if, timeout, async, shell}. Das innere hooks-Array ist kein Typo — der äußere Block bündelt nach matcher (Regex), das innere sind die tatsächlichen Ausführungseinheiten. Event-Namen: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, SessionEnd, SubagentStart/Stop, FileChanged, CwdChanged usw.

Headless-Verhalten: Im -p-Modus gilt das gleiche Schema, aber ohne interaktive Prompts. Wenn ein Tool nicht durch permissions.allow gedeckt ist und kein --allowedTools angegeben ist, bricht der Run ab statt zu fragen. Für nicht-interaktive Tool-Approval kann --permission-prompt-tool <mcp-tool> gesetzt werden (MCP-basiert). Für DisClaw reicht: pro Agent ein kuratiertes permissions.allow + ggf. defaultMode: "acceptEdits".

Quelle / Konfidenz: Hoch. code.claude.com/docs/en/settings (Permission settings-Tabelle) und code.claude.com/docs/en/hooks.

Implikation für DisClaw: In Phase 4 (Settings-Schema pro Agent) schreiben wir .claude/settings.json ins Agent-Workspace mit permissions.allow / permissions.deny und — falls gewünscht — defaultMode: "acceptEdits" für Auto-Approve beim Editieren. Hooks-Struktur dokumentieren wir mit dem dreistufigen Layout. Headless-Gotcha im README erwähnen: ohne Allow-Rules brechen Tool-Calls ab.


4. --output-format stream-json Event-Schema

Antwort: Die CLI emittiert einen NDJSON-Stream. Jede Zeile ist ein JSON-Objekt mit einem type-Feld. Relevante Typen (aus der Doku bestätigt):

  • system mit subtype (z. B. init, api_retry) — Metadaten, Retry-Events
  • assistant — Tool-Use-Blöcke und Text-Blöcke vom Modell (Native-API-Format)
  • user — Tool-Results
  • stream_event — Partial-Streaming-Deltas (nur wenn --include-partial-messages gesetzt ist). Text-Deltas landen unter .event.delta.type == "text_delta" mit .event.delta.text.
  • result — Final-Event mit dem zusammengefassten Assistant-Response, session_id, Usage-Stats etc.
  • Hook-Events (wenn --include-hook-events gesetzt)

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.

Unsicherheit: Das exakte Feld-Layout der assistant / user / tool_use Message-Typen folgt dem Native-Anthropic-API-Format (content-Blocks mit type: text / type: tool_use / type: tool_result). Wir sollten den Parser defensiv schreiben — die Typen sind über CLI-Versionen hinweg in der groben Struktur stabil, aber Details (neue Felder, neue Subtypes wie api_retry) können dazukommen. Die Doku sagt nicht explizit "stable API", und die Flag-Tabelle warnt am Anfang sogar, dass nicht jeder Flag im --help auftaucht.

Quelle / Konfidenz: Mittel-hoch. code.claude.com/docs/en/headless (Stream-Responses-Abschnitt), plus Doku-Beispiel mit jq '.type == "stream_event" and .event.delta.type? == "text_delta"'.

Probe-Command (zur Schema-Verifikation):

claude -p "schreibe ein haiku" \
  --output-format stream-json --verbose --include-partial-messages --bare \
  | jq -c '{type, subtype, event_type: .event.type?, delta_type: .event.delta.type?}'

Implikation für DisClaw: Phase 5 (Streaming nach Discord) sollte den Parser nach type dispatchen und robust gegen unbekannte Typen sein (skip statt crash). Für Discord-Live-Editing reicht: akkumuliere text_delta-Chunks und editiere die Bot-Nachricht im 1-2s-Rhythmus (Discord Rate-Limit). Bei result-Event commitet man den finalen Stand. Tool-Use-Blocks kann man separat als Embed anzeigen ("🔧 Read(foo.ts)"), aber das ist V2.


5. Prompt über stdin

Antwort: Ja, stdin wird unterstützt, aber mit einer Eigenart. Die CLI lauscht auf stdin, wenn sie Piped-Input erkennt. Die Doku zeigt das Muster:

cat logs.txt | claude -p "explain"

Wichtig: Der gepipte Text wird als zusätzlicher Context angehängt, nicht als Ersatz für -p. Der Prompt muss weiterhin per -p "..." kommen. Es gibt kein dokumentiertes claude -p - oder --input - für "lies Prompt komplett von stdin". Für Bulk/Streaming-Input existiert --input-format stream-json (kombiniert mit --output-format stream-json), das JSON-Messages auf stdin erwartet — das ist der SDK-Modus und deutlich mehr Komplexität als DisClaw braucht.

Unsicherheit: Mittel. Das "Prompt komplett über stdin" ist nicht dokumentiert. Muss empirisch geprüft werden, falls wir auf Windows ARG_MAX-Probleme sehen (Windows CreateProcess hat ~32k Zeichen Limit pro Kommandozeile).

Probe-Command:

# Variante A: Prompt per stdin, kein -p-String
echo "sag hallo" | claude -p --bare --output-format json
# Variante B: Prompt als -p, zusätzlich Kontext auf stdin
echo "extra context" | claude -p "fasse zusammen" --bare --output-format json

Implikation für DisClaw: Für Phase 3/4 planen wir:

  1. Primär: -p "<discord message>" direkt — Discord-Messages sind auf 2000 Zeichen gecappt, ARG_MAX ist kein Thema.
  2. Für lange Prompts / Attachments / Multi-Message-Kontext: pipe via stdin (geprüfter Pfad A) und halte Prompt-Text NICHT als shell-interpolierten String — stattdessen child_process.spawn mit argv-Array (kein shell: true), so ist Command-Injection ohnehin ausgeschlossen. stdin ist dann primär nützlich, wenn wir Datei-Inhalte beifügen wollen.

Command-Injection ist bereits durch spawn(cmd, args, {shell: false}) eliminiert, nicht erst durch stdin. stdin hilft nur gegen ARG_MAX.


6. Bild-/Attachment-Syntax

Antwort: Im -p-Modus gibt es keinen dedizierten --attach-Flag in der dokumentierten Flag-Tabelle. Der Weg ist: Bild-Datei im Workspace ablegen und im Prompt per @./path/to/image.png referenzieren. Die CLI erkennt @-Pfade als Datei-Referenzen und lädt sie (bei Bildern als Vision-Input, bei Text als Datei-Inhalt). Das gilt allerdings vor allem für interaktive Sessions — das @-Picker-UI ist interaktiv, der Syntax @./pfad als Inline-Token im Prompt-Text funktioniert aber auch headless.

Unsicherheit: Mittel. Die Doku lässt offen, ob @./image.png im -p-Prompt garantiert als Vision-Input geladen wird oder nur als Pfad-Literal. Sicher funktioniert: Der Prompt enthält den Pfad und Claude liest das Bild via Read-Tool (falls in allowedTools). Das ist ein Tool-Use-Round-Trip, kein direkter Vision-Input.

Probe-Command:

claude -p "Beschreibe das Bild: @./test.png" \
  --allowedTools "Read" --output-format json --bare

Falls das nicht als direkter Vision-Input zählt, ist der saubere Weg: Agent SDK (Python/TypeScript) statt CLI--p — dort gibt es input_image-Blöcke im Message-Content.

Implikation für DisClaw: Für Phase 3/4 reicht die Strategie:

  1. Discord-Attachment herunterladen in workspaces/<agent>/.inbox/<ts>-<name>
  2. Prompt bauen: "Der User hat ein Bild geschickt: @./.inbox/<ts>-<name>.png. Analysiere es."
  3. Claude nutzt Read-Tool + Vision intern.

Für echtes Multimodal-Passthrough mit garantiertem Vision-Input ohne Tool-Round-Trip müssten wir später auf das Agent SDK wechseln (Phase 6+). Das gehört in den Development-Plan als Option, nicht als Blocker.


7. Prompt-Caching messbar

Antwort: Ja — 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. Das gleiche Schema wie der Messages-API-Response. Auch stream-json liefert die Usage-Felder im result-Event am Ende.

Unsicherheit: Mittel. Die Doku-Tabelle listet nicht explizit die Usage-Feldnamen im CLI-JSON-Output, aber der --max-budget-usd-Flag und die Tatsache, dass die CLI auf dem gleichen API-Layer aufsetzt, machen es sicher, dass die Felder da sind. 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.

Probe-Command:

# Erste Call - Cache Creation
claude -p "analysiere dieses repo" --output-format json --bare > run1.json
# Folge-Call mit Resume - sollte Cache Reads haben
sid=$(jq -r '.session_id' run1.json)
claude -p "und jetzt die tests?" --resume "$sid" --output-format json --bare > run2.json

jq '.usage // .result.usage // .' run1.json
jq '.usage // .result.usage // .' run2.json
# Erwartet: run2 hat cache_read_input_tokens > 0

Implikation für DisClaw: Ideal für ein kleines Metrik-Logging pro Agent-Run (cache_read, cache_create, total cost). Kommt in Phase 4/5 als Nice-to-have: SQLite-Tabelle agent_runs mit Token-Stats pro Turn. Liefert Beleg, ob --resume tatsächlich Cache-Effekt bringt (und rechtfertigt die Session-Persistenz).


8. Hierarchisches CLAUDE.md-Merging

Antwort: Kritischer Punkt für DisClaw. Die Doku sagt explizit:

"Claude Code reads CLAUDE.md files by walking up the directory tree from your current working directory, checking each directory along the way for CLAUDE.md and CLAUDE.local.md files. [...] All discovered files are concatenated into context rather than overriding each other."

Das bedeutet: Wenn Agent-Workspace unter C:\Code\side\disclaw\workspaces\my-agent\ liegt und cwd darauf gesetzt ist, lädt Claude Code zusätzlich das DisClaw-Projekt-CLAUDE.md aus C:\Code\side\disclaw\ und — falls vorhanden — C:\Code\side\CLAUDE.md und so weiter bis zum Laufwerks-Root. Ebenfalls ~/.claude/CLAUDE.md (User-Scope) und managed policy CLAUDE.md.

Außerdem: ./.claude/rules/*.md in Parent-Verzeichnissen werden gleich behandelt.

Wie verhindern? Drei dokumentierte Wege:

  1. --bare: Skippt CLAUDE.md-Discovery komplett (plus Hooks, Skills, Plugins, MCP, Auto-Memory). Das ist der empfohlene Modus für scripted und SDK-Calls, und die Doku sagt explizit: "will become the default for -p in a future release". Für DisClaw: Goldstandard, weil es volle Reproduzierbarkeit gibt — Agent sieht genau das, was wir ihm geben. Nachteil: Auto-Memory, Hooks und Plugins sind auch aus. Für DisClaw ist das ok, weil wir die Identität über --append-system-prompt-file oder explizites Laden der Agent-CLAUDE.md selbst beisteuern.

  2. claudeMdExcludes im Agent-settings.local.json:

    {
      "claudeMdExcludes": [
        "C:/Code/side/disclaw/CLAUDE.md",
        "C:/Code/side/disclaw/.claude/rules/**"
      ]
    }
    

    Absolute Glob-Pfade. Merged über alle Scopes. Managed policy CLAUDE.md kann NICHT exkludiert werden — irrelevant solange wir keinen installieren.

  3. Workspaces außerhalb des DisClaw-Repos legen: Statt disclaw/workspaces/ z. B. ~/disclaw-workspaces/<agent>/. Dann gibt es oberhalb keine Projekt-CLAUDE.md (nur User-Scope ~/.claude/CLAUDE.md und ggf. managed).

Quelle / Konfidenz: Hoch. code.claude.com/docs/en/memory Abschnitt "How CLAUDE.md files load" und "Exclude specific CLAUDE.md files".

Implikation für DisClaw: Das zwingt eine Plan-Anpassung. Zwei Empfehlungen kombinieren:

  1. Workspaces außerhalb des DisClaw-Repos als Default speichern (~/.disclaw/workspaces/<agent>/ oder konfigurierbar via disclaw.yaml workspace_root). Das eliminiert das Parent-CLAUDE.md-Problem strukturell. disclaw/workspaces/ bleibt nur für Entwicklung/Tests.
  2. Zusätzlich --bare verwenden für alle Headless-Calls und Agent-Identity explizit via --append-system-prompt-file <workspace>/CLAUDE.md anhängen. Das ist reproduzierbar, schnell (weniger Startup-Cost), und zukunftssicher (wird ohnehin Default für -p). Die Agent-CLAUDE.md wird dann im -p --bare-Modus nicht via Memory-Mechanismus geladen, sondern direkt als System-Prompt-Append eingespeist.
  3. Für Agenten, die trotzdem im DisClaw-Repo unter workspaces/ liegen müssen (z. B. Git-Tracking), zusätzlich claudeMdExcludes im settings.json des Agents setzen.

Der bisherige Plan, workspaces/<agent>/CLAUDE.md als einzige Identitäts- Quelle zu nutzen, ist auf zwei Arten fragil:

  • Wenn der Agent unter einem DisClaw-Projekt-Root läuft, sieht er beides.
  • Wenn eine ~/.claude/CLAUDE.md existiert (beim Entwickler), leakt die in jeden Agent.

9. Windows-Spezifika ohne shell: true

Antwort: Auf Windows ist claude.cmd (kein claude.exe) der Wrapper, den npm install -g @anthropic-ai/claude-code ablegt — typisch unter %APPDATA%\npm\claude.cmd oder dem npm-global-Prefix. Node.js child_process.spawn ohne shell: true auf Windows hat folgende Regeln:

  1. Wenn man spawn("claude", args) ruft, scannt Node die PATH-Einträge nach einer exakten Datei namens claude. Da nur claude.cmd existiert (keine Datei ohne Extension), schlägt das fehl. Deshalb muss man explizit spawn("claude.cmd", args) oder einen absoluten Pfad übergeben.
  2. .cmd- und .bat-Dateien sind Batch-Skripte, die normalerweise nur von cmd.exe ausgeführt werden können. Node.js hat dafür seit Jahren einen Work-around, aber seit Node 20.12 (CVE-2024-27980) ist spawn("foo.cmd") ohne shell: true auf Windows entweder standardmäßig gesperrt oder erfordert { shell: true } plus Escaping der Argumente. Dies ist die größte Stolperfalle.

Empfohlene robuste Lösung für DisClaw:

  1. Beim Startup auflösen: Einmalig where claude (Windows) bzw. which claude (Unix) ausführen und den absoluten Pfad zur .cmd/.sh auflösen und in Config cachen. Der User kann das auch explizit über CLAUDE_PATH in .env überschreiben.
  2. Für .cmd auf Windows: Statt direkt zu spawnen, suchen wir das .js-Target hinter dem .cmd (npm generiert claude.cmd als Wrapper, der node <path-to-cli.js> aufruft) und spawnen node cli.js args... direkt. Das umgeht sowohl CVE-2024-27980 als auch ARG_MAX-Interpretation durch cmd.exe.
  3. Alternative (einfacher): Nutze die Library cross-spawn (npm), die genau dieses Problem abstrahiert. Intern löst sie .cmd-Wrapper auf, quoted Windows-Args korrekt, und funktioniert identisch auf allen Plattformen. Für DisClaw deutlich pragmatischer als eigene Logik.

windowsHide: true setzen, damit kein Konsolen-Fenster aufpoppt. stdio: ["pipe", "pipe", "pipe"].

Encoding: cmd.exe nutzt standardmäßig CP1252/CP850, nicht UTF-8. Das ist irrelevant, wenn wir direkt node cli.js spawnen (keine cmd.exe dazwischen) oder cross-spawn nutzen. Wenn doch: CHCP 65001 in einem Wrapper oder env: { ...process.env, PYTHONIOENCODING: 'utf-8' } hilft nicht — der Fix ist child.stdout.setEncoding("utf8") und dafür zu sorgen, dass die CLI selbst UTF-8 schreibt. Die Claude-CLI tut das, weil sie JSON ausgibt und Node-basiert ist — UTF-8 ist der Default.

Quelle / Konfidenz: Hoch für Node.js-Verhalten und CVE-2024-27980 (Node Security Advisory, Q2 2024). Für Claude-CLI-Wrapper-Struktur mittel — ist npm-Standard, aber nicht Anthropic-dokumentiert.

Probe-Command:

# finde die echte CLI-Entry
where claude
cat "$(where claude | head -1)"   # zeigt den .cmd-Wrapper-Inhalt
# typisch: ruft node "%~dp0\node_modules\@anthropic-ai\claude-code\cli.js" %*

Implikation für DisClaw: In src/agent/runner.ts:

  1. Bei Bot-Start: CLAUDE_PATH aus .env lesen; falls leer, where claude (Windows) / which claude (Unix) automatisch ausführen und Pfad in-memory cachen (nicht persistieren).
  2. Auf Windows: parse den .cmd-Wrapper einmal, extrahiere den node <cli.js>-Call, und spawne künftig direkt spawn("node", ["<cli.js>", ...args], { windowsHide: true, shell: false }). Oder verwende cross-spawn für denselben Effekt ohne Eigenbau.
  3. stdout als UTF-8 dekodieren (child.stdout.setEncoding("utf8")), auf stderr separat loggen.
  4. README//setup-Command: klare Fehlermeldung, wenn where claude nichts findet, mit Hinweis auf CLAUDE_PATH im .env.

10. Session-Datei-Lebenszyklus

Antwort: Session-JSONL-Dateien liegen unter

~/.claude/projects/<project-slug>/<session-uuid>.jsonl

<project-slug> ist kein SHA-Hash, sondern der absolute cwd-Pfad mit allen Pfad-Separator-Zeichen ersetzt durch -. Auf Windows: C:, \, /, :-. Beispiel (empirisch auf diesem System verifiziert):

cwd = C:\Code\side\disclaw
slug = C--Code-side-disclaw
file = ~/.claude/projects/C--Code-side-disclaw/<uuid>.jsonl

Zusätzlich existiert pro Projekt-Slug ein memory/-Unterverzeichnis für Auto-Memory (MEMORY.md + Topic-Files).

Kein offizielles CLI-Kommando zum Enumerieren oder Löschen von Sessions eines cwd ist dokumentiert. Was es gibt:

  • cleanupPeriodDays Setting (default 30 Tage): Sessions älter als X Tage werden beim Startup automatisch gelöscht.
  • --no-session-persistence: Session wird gar nicht erst auf Disk geschrieben.
  • Interaktiv: /resume zeigt einen Picker aller Sessions für den aktuellen cwd. Aber das ist interaktiv-only und nicht skriptbar.
  • --resume <id-or-name> nimmt eine bekannte ID.

Für DisClaw /delete-agent müssen wir direkt im Dateisystem operieren: den Projekt-Slug berechnen (gleiche Transformation: absolute cwd → ersetzen), das Verzeichnis ~/.claude/projects/<slug>/ prüfen und löschen. Das ist sauber genug, weil das Format dokumentiert ist ("Each project gets its own memory directory at ~/.claude/projects/<project>/memory/") auch wenn die exakte Slug-Transformation nicht wortwörtlich beschrieben ist — wir haben sie aber empirisch bestätigt.

Unsicherheit: Niedrig für Windows (empirisch auf diesem System auf C--Code-side-disclaw beobachtet). Mittel für Linux/Mac: dort ist der Separator /, der cwd beginnt mit /, die Transformation liefert also Slugs wie -home-nick-disclaw-workspaces-agent1. Das ist eine vernünftige Annahme, sollte aber plattformspezifisch geprüft werden.

Probe-Command:

# Windows/Git-Bash
ls ~/.claude/projects/ | head
# auf Linux
ls ~/.claude/projects/ | head
# Gegen-Check: nach einem claude -p in einem neuen cwd sollte ein neuer slug-Ordner existieren
mkdir -p /tmp/sesstest && cd /tmp/sesstest && claude -p "hallo" --bare --output-format json
ls ~/.claude/projects/ | grep sesstest

Implikation für DisClaw:

  1. In src/agent/identity.ts eine Funktion cwdToProjectSlug(absCwd: string): string bauen, die die Transformation nachbildet. Test auf beiden Plattformen.
  2. /delete-agent löscht: (a) Workspace-Dir, (b) DB-Row, (c) ~/.claude/projects/<slug>/ (mit optionalem Opt-out-Flag, falls User die Auto-Memory behalten will).
  3. Session-Enumeration für ein Feature wie /agent-history parsen wir die JSONL-Dateien direkt (sind Newline-getrenntes JSON).
  4. Setzen cleanupPeriodDays im Agent-settings.json z. B. auf 90 für persistente Agent-Historie.

Plan-Anpassungen

Die folgenden drei Punkte zwingen eine Änderung im development-plan.md:

A. Frage 8 (CLAUDE.md-Hierarchie) — architektonisch kritisch

Aktuelle Annahme im Plan: workspaces/<agent>/CLAUDE.md ist die einzige Identitäts-Quelle.

Realität: CLAUDE.md wird parent-walked; wenn Workspace im DisClaw-Repo liegt, sieht jeder Agent auch die DisClaw-Projekt-CLAUDE.md und die User-~/.claude/CLAUDE.md.

Plan-Anpassung:

  • Neu Phase 1.5 (vor Phase 2): Workspace-Root-Strategie festlegen. Default: ~/.disclaw/workspaces/ (außerhalb DisClaw-Repo). Konfigurierbar in disclaw.yaml. In Dev-Modus ./workspaces/ erlaubt, aber mit Warnung.
  • Phase 2 (Session-Resume): CLI-Calls müssen --bare verwenden. Agent-Identity wird per --append-system-prompt-file <ws>/CLAUDE.md angehängt. Das macht Agent-Identität explizit und unabhängig von Parent-CLAUDE.md-Leaks.
  • Phase 4 (Settings-Schema): settings.json pro Agent schreiben mit optionalem claudeMdExcludes für Dev-Mode-Workspaces im DisClaw-Repo.

B. Frage 9 (Windows .cmd ohne shell: true) — Implementierung

Aktuelle Annahme: spawn("claude", args, { shell: false }) funktioniert.

Realität: Auf Windows muss claude.cmd explizit aufgelöst werden, und CVE-2024-27980 blockt direktes .cmd-Spawning ohne shell: true.

Plan-Anpassung:

  • Phase 1 (Bot-Skeleton): src/agent/runner.ts initial mit cross-spawn statt nacktem child_process.spawn implementieren — das ist 1 Zeile Dependency und spart 50 Zeilen Windows-Workaround-Code.
  • Alternative: where claude beim Bot-Start einmal ausführen, den .cmd-Wrapper parsen, und direkt node <cli.js> spawnen. Mehr Code, aber keine extra Dependency. Entscheidung für den Entwickler — Empfehlung: cross-spawn.
  • /setup-Skill: Fehlermeldung wenn claude nicht auf PATH.

C. Frage 6 (Attachments) — Scope-Anpassung

Aktuelle Annahme (vermutlich): Bilder aus Discord werden direkt als Vision-Input an Claude gegeben.

Realität: CLI--p-Modus hat keinen dokumentierten --attach-Flag. Workaround über @./path-Syntax und Claude's Read-Tool funktioniert, erfordert aber allowedTools: "Read" und ein Tool-Use-Round-Trip.

Plan-Anpassung:

  • Phase 3/4 (Attachments): Dokumentiere den Workaround-Ablauf klar. Bilder landen in <ws>/.inbox/, Prompt referenziert per @./.inbox/..., allowedTools enthält Read. Echter Multimodal-Passthrough ohne Tool-Round-Trip ist Phase 6+ (Agent SDK-Migration), nicht jetzt.

Unkritisch, aber relevant für den Plan

  • Frage 2 (Session-ID Stabilität): Wir persistieren die ID nach jedem Run defensiv neu (günstig, transparent gegen Edge-Cases).
  • Frage 7 (Cache-Metrik): Nice-to-have agent_runs-Tabelle in SQLite für Token-Stats pro Turn. Belegt den Nutzen von --resume-Caching.
  • Frage 10 (Session-Datei-Lifecycle): /delete-agent räumt ~/.claude/projects/<slug>/ mit auf; Slug-Berechnung als eigene getestete Funktion.

Nicht betroffen (Plan kann wie gedacht fortfahren):

  • Frage 1 (-p + --resume): funktioniert wie geplant.
  • Frage 3 (Permissions-Schema): Schema klar, Phase 4 kann starten.
  • Frage 4 (stream-json): Parser defensiv bauen, Phase 5 kann starten.
  • Frage 5 (stdin): ARG_MAX ist praktisch kein Thema bei Discord-2k-Limits.