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

605 lines
27 KiB
Markdown

# 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:
```bash
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):
```bash
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:
```json
{
"$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):
```bash
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:
```bash
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**:
```bash
# 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**:
```bash
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**:
```bash
# 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`:
```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**:
```bash
# 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**:
```bash
# 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.