46 KiB
DisClaw — Entwicklungsplan
Autor: Senior Developer
Datum: 2026-04-08
Basis: ARCHITECTURE.md, CLAUDE.md, docs/ai-engineer-analyse.md, docs/cli-feature-answers.md, aktueller Stand unter src/.
Der Plan ist umsetzungsnah. Er legt fest, was gebaut wird, wie, in welcher Reihenfolge, und wo der Code erweiterbar bleiben muss. Zeitangaben gibt es bewusst nicht — nur logische Reihenfolge und Abhängigkeiten.
Revision-Log
- 2026-04-08: Initialversion.
- 2026-04-08: AI-Engineer-Antworten eingearbeitet (siehe
docs/cli-feature-answers.md). Workspace-Location auf~/.disclaw/workspaces/(außerhalb DisClaw-Repo) geändert, Windows-Spawn-Fix viacross-spawnergänzt, Phase 2 (--resume) freigegeben,.claude/settings.json-Schema und Hooks-Layout präzisiert, Abschnitt 6 durch Verweis auf Antworten-Dokument ersetzt.
1. Status Quo
Was existiert und funktioniert (vermutlich)
src/index.ts— Bootstrap, lädt Env und Config, initialisiert DB, startet Bot, Graceful Shutdown.src/config/loader.ts— Lädt.envunddisclaw.yaml.src/db/database.ts—better-sqlite3-Wrapper,workspacesundconversationsTabellen mit Prepared Statements, Migrationen viaIF NOT EXISTS, WAL und Foreign Keys aktiv.src/db/schema.sql— SQL-Schema (separat vorhanden, aber Migration läuft aktuell aus dem Inline-String indatabase.ts, Duplikation).src/bot.ts— Discord Client mit IntentsGuilds,GuildMessages,MessageContent. Auto-Erstellung/Adoption des Management-Channels. Slash-Command-Registrierung pro Guild. BasicmessageCreateDispatch.src/router.ts— Channel→Workspace Lookup, naives 2000-Zeichen-Splitting an Line-Breaks, Typing-Indikator alle 5 s, schreibt User- und Assistant-Messages inconversations.src/commands/new-agent.ts— Slash-Command/new-agentmit Name-Validierung (Regex), Channel-Create, Workspace-Erstellung, DB-Insert.src/agent/identity.ts—agent.yaml,CLAUDE.mdund.claude/settings.jsonTemplates werden beim Create geschrieben.src/agent/runner.ts— Spawntclaude -p "<prompt>" --output-format json, baut History als Freitext, parst JSON-Output in drei Formaten, Timeout, AbortController-Support.
Was kaputt oder problematisch ist
- P0 Security:
src/agent/runner.ts:187setztshell: true— direkter Command-Injection-Vektor über Discord-User-Input (siehe AI-Engineer-Analyse §4.1). - P0 Secret Leak:
src/agent/runner.ts:181vererbtprocess.envvollständig an den Kindprozess →DISCORD_BOT_TOKENund andere Secrets sind im Claude-Prozess sichtbar (§4.5). - Kein Queueing pro Channel: Mehrere gleichzeitige Nachrichten im selben Channel starten parallele
claude-Prozesse im gleichencwd. Race Conditions bei Tool-Calls und Session-Updates sind garantiert (§2 "Per-Channel-FIFO-Queue"). - Agent-Name-Regex unzureichend gegen Path Traversal: Kein expliziter
path.resolve-Containment-Check innew-agent.ts. Regex akzeptierta-aaber kein explizites Path-Check-Gate existiert (§4.3). - Message-Splitting zerreißt Codeblöcke:
router.ts:splitMessage()respektiert keine``` Fences (§2). - History-Injektion statt
--resume:runner.ts:buildPrompt()konkateniert Historie als Freitext — kein Prompt-Caching, falsche Rollenzuordnung (§1). .claude/settings.jsonTemplate ist zu permissiv:allow: ["Read", "Write", "Edit", "Bash", ...]ohne Pfad-Restriktionen und ohnedeny-Liste. Kein Hook-Guard.CLAUDE.mdTemplate hat keine Prompt-Injection-Resistenz-Klausel.- Keine User-Rate-Limits, kein globales Concurrency-Cap.
- Keine Tests. Kein
devDependencies-Eintrag für ein Test-Framework. - Keine strukturierten Logs. Nur
console.log. Schwierig zu filtern/parsen. - Keine
.env.exampleim Repo (zu verifizieren beils). - Doppelte Schema-Quelle:
src/db/schema.sqlund Inline-SCHEMA_SQLindatabase.tskönnen divergieren. - Typing-Refresh auf 5 s: Discord-Typing läuft ~10 s, 5 s ist okay aber unnötig häufig; 8 s reicht (§2).
Was komplett fehlt
/list-agents,/delete-agentCommands.- Session-ID-Persistenz (
--resume). - Streaming-Output.
- Attachment-Handling.
- PreToolUse-Hook / Audit-Log.
- Profile-basiertes Workspace-Scaffolding (researcher/developer/writer/ops).
- Per-Channel-Queue, globaler Concurrency-Semaphore.
- Structured Logger (pino o. ä.).
- Zod-Validation für Config und CLI-JSON-Output.
- Tests (Unit und Integration).
docs/cli-feature-probe.md— Test-Script, das das tatsächliche Verhalten der installiertenclaudeCLI gegen unsere Annahmen verifiziert.
2. Technische Entscheidungen (locked in)
2.1 Context-Persistenz: Hybrid (--resume primär, DB-Rehydrierung Fallback)
Was: Session-ID pro Workspace in der DB persistieren. Runner ruft claude -p "<msg>" --resume <session-id> --output-format json --bare auf. Wenn keine Session-ID existiert oder --resume fehlschlägt, wird aus den letzten N DB-Einträgen ein Bootstrap-Prompt gebaut und eine neue Session gestartet. Die neue session_id aus dem JSON-Output wird nach jedem Run in der DB aktualisiert (defensiv gegen Fork-Verhalten älterer CLI-Versionen).
Warum: Prompt Caching von Anthropic greift nur bei stabilen Präfixen innerhalb einer Session. Neue Session pro Nachricht = 0 % Cache-Hit, jede CLAUDE.md wird neu tokenisiert. Mit --resume erwartete Einsparung 60–90 % Input-Tokens (siehe ai-engineer-analyse.md §1). Bestätigt über cli-feature-answers.md Frage 1+2: -p + --resume ist offiziell dokumentiert, session_id liegt top-level im JSON-Output.
Wie:
- Neue DB-Spalten in
workspaces:claude_session_id TEXT,session_updated_at TEXT. Migration additiv (ALTER TABLE ... ADD COLUMN). - Runner-API bekommt einen
SessionStore-Typ injiziert (damit der Runner testbar bleibt). - Session-ID lesen:
JSON.parse(stdout).session_id(top-level). Nach jedem Run perstore.set(ws.id, json.session_id)persistieren, auch wenn die ID identisch zur vorigen ist — fängt Fork-Verhalten (--fork-session, CLI-Upgrades,cleanupPeriodDays-Expiry) transparent auf. - Fallback bei
--resumeFehler: Error-String aufsession .* not foundoder ähnlich matchen →claude_session_id = NULLsetzen und erneut starten ohne--resume(optional mit--continueals zweiter Fallback-Stufe). - Die
conversations-Tabelle bleibt Source of Truth für Rehydrierung nach Bot-Neustart oder vollständigem Session-Verlust. - Metriken:
usage-Block aus JSON-Output parsen (cache_read_input_tokens,cache_creation_input_tokens,input_tokens,output_tokens) und pro Run loggen. Exakter Pfad (result.usage.*oder top-levelusage.*) wird empirisch via Probe-Script verifiziert.
// src/agent/runner.ts (Skizze)
export interface SessionStore {
get(workspaceId: number): string | null;
set(workspaceId: number, sessionId: string): void;
clear(workspaceId: number): void;
}
async function runOnce(ws: Workspace, msg: string, store: SessionStore): Promise<RunResult> {
const sid = store.get(ws.id);
const args = [
"-p", msg,
"--output-format", "json",
"--bare",
"--append-system-prompt-file", path.join(ws.workspace_path, "CLAUDE.md"),
];
if (sid) args.push("--resume", sid);
const res = await spawnClaude(args, ws.workspace_path);
if (res.ok) {
// Immer updaten — fängt Fork/Cleanup-Edge-Cases ab
if (res.sessionId) store.set(ws.id, res.sessionId);
return res;
}
if (sid && isResumeFailure(res.error)) {
store.clear(ws.id);
return runOnce(ws, msg, store); // einmaliger Retry ohne --resume
}
throw res.error;
}
Status: Freigegeben. cli-feature-answers.md Frage 1+2 bestätigt, dass -p "..." --resume <id> --output-format json offiziell dokumentiert ist und session_id als Top-Level-Feld im JSON-Output liegt. Kein Blocker mehr. Das Probe-Script verifiziert lediglich noch die genauen Pfade des usage-Blocks und bleibt als Regression-Check im Repo.
2.2 Prozessmodell: Spawn pro Nachricht (beibehalten)
Was: Ein Bot-Prozess, ein claude-Kindprozess pro Nachricht, terminiert nach Antwort.
Warum: Persistente Prozesse sind im MVP nicht vertretbar (150–300 MB RSS pro idle Agent, Supervisor nötig, Windows-Signal-Semantik fragil, interaktiver Modus ist TTY-first). Spawn-pro-Nachricht ist einfacher, isolierter, crash-resistenter. Der Kaltstart-Overhead wird durch --resume + Prompt Caching teilweise kompensiert.
Wie: Bestehender runner.ts-Ansatz, aber mit shell: false, sanitizedEnv, Windows-Pfad-Auflösung, SessionStore, und abstrahiertem Event-Stream-Return-Type, damit Phase 5 (Streaming) ohne API-Bruch nachgerüstet werden kann.
Windows-Spawn-Entscheidung (locked in): cross-spawn als Dependency. Grund: claude wird auf Windows als claude.cmd-Wrapper installiert (%APPDATA%\npm\claude.cmd), nicht als .exe. Seit Node 20.12 (CVE-2024-27980) blockt Node child_process.spawn("foo.cmd", args, { shell: false }) bzw. verlangt explizites Arg-Quoting — spawn("claude", args) scheitert außerdem, weil Node ohne shell: true keine .cmd-Extension im PATH-Lookup anhängt. cross-spawn abstrahiert beides (löst .cmd-Wrapper auf, quoted Args korrekt, identisches Verhalten Linux/Mac/Windows).
Trade-off: Eine Mini-Dependency (~30 kB, keine transitiven Deps) gegen ~50 Zeilen Windows-Workaround-Code (where-lookup + .cmd-Wrapper-Parsing + direkter node cli.js-Spawn). Die Eigenbau-Variante bleibt als Fallback dokumentiert, falls cross-spawn jemals ausfällt, aber ist für den MVP nicht die erste Wahl — der Pragmatik-Gewinn wiegt die Dependency auf.
Zusätzlich: Beim Bot-Start einmalig CLAUDE_PATH aus .env lesen; falls leer, where claude (Windows) / which claude (Unix) automatisch ausführen, Ergebnis in-memory cachen. Fehlermeldung mit klarem Hinweis auf CLAUDE_PATH, falls nicht auffindbar. windowsHide: true, stdio: ["pipe", "pipe", "pipe"], child.stdout.setEncoding("utf8").
2.3 Per-Channel-Queue
Was: FIFO-Queue pro channel_id, die alle Runs im gleichen Channel serialisiert. Parallele Channels bleiben parallel.
Warum: Siehe §1.3 Status Quo — ohne Queue laufen race-anfällige parallele Runs im selben Workspace.
Wie: In-Memory Map<channelId, Promise<void>> im Router. Neue Tasks werden an die letzte Promise der Queue angehängt. Kein externer Queue-Service, kein Redis — MVP-tauglich. Zusätzlich: globaler Semaphore (max. 4 parallele claude-Prozesse) über ein simples Counting-Primitive.
// src/runtime/channel-queue.ts
export class ChannelQueue {
private queues = new Map<string, Promise<unknown>>();
enqueue<T>(channelId: string, task: () => Promise<T>): Promise<T> {
const prev = this.queues.get(channelId) ?? Promise.resolve();
const next = prev.catch(() => undefined).then(task);
this.queues.set(channelId, next);
next.finally(() => {
if (this.queues.get(channelId) === next) this.queues.delete(channelId);
});
return next;
}
}
2.4 Message-Splitting: Eigenbau, codeblock-aware
Was: Eigene Funktion splitForDiscord(text, limit=1900), die Fences ( ) erkennt, offene Blöcke beim Split schließt und im nächsten Chunk wieder öffnet. Bei Ausgaben >8 k Zeichen: stattdessen als .md-Attachment senden.
Warum: Keine externe Library erfüllt das 1:1 (die üblichen "split-to-discord"-Pakete sind unmaintained oder fehlerhaft bei Fences). Eigenbau ist ~60 Zeilen und testbar. Limit 1900 statt 2000 lässt Puffer für Prefix (1/3).
Wie: Siehe Algorithmus-Skizze in docs/ai-engineer-analyse.md §2 "2000-Zeichen-Splitting". Ablegen unter src/discord/split.ts mit Unit-Tests.
2.5 Workspace-Isolation: Permissions + PreToolUse-Hook
Was: .claude/settings.json bekommt ein Default-Deny für Pfade außerhalb ./ und für gefährliche Bash-Muster. Zusätzlich PreToolUse-Hook, der jeden Tool-Call mit absolutem Pfad gegen workspace_abs_path matched und abbricht, wenn außerhalb.
Warum: Permissions allein reichen nicht, weil Read(./**) sich mit cd .. via Bash aushebeln lässt. Der Hook ist die zweite Verteidigungslinie und gleichzeitig der Audit-Log-Hook.
Wie:
src/agent/templates/settings.default.jsonals Template mit Platzhalter{{workspace_abs}}.- Schema-Struktur wie in
cli-feature-answers.mdFrage 3 bestätigt: Permissions unterpermissions.allow/permissions.ask/permissions.deny, mit zusätzlichpermissions.defaultModeundpermissions.additionalDirectories. Hooks als dreistufige Struktur:hooks.<EventName>[].matcher+hooks.<EventName>[].hooks[].{type, command, if, timeout}. - Hook-Script als Node-File:
src/agent/hooks/guard-tool.ts, das beim Workspace-Create nach./.claude/hooks/guard-tool.cjsgerendert wird (compile-to-CJS damit der Hook ohne Build-Step ausführbar ist). - Template-Beispiel (das tatsächliche Schema):
{ "$schema": "https://json.schemastore.org/claude-code-settings.json", "permissions": { "allow": ["Read", "Write(./**)", "Edit(./**)", "Bash(git diff *)"], "ask": ["Bash(git push *)"], "deny": [ "Read(../**)", "Write(../**)", "Edit(../**)", "Read(./.env)", "WebFetch", "Bash(rm -rf *)", "Bash(curl * | sh)", "Bash(ssh *)", "Bash(scp *)", "Bash(* /etc/*)", "Bash(* ~/.ssh/*)", "Bash(powershell -Command *)" ], "defaultMode": "acceptEdits" }, "hooks": { "PreToolUse": [ { "matcher": "Bash|Read|Write|Edit", "hooks": [ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-tool.cjs\"", "timeout": 10 } ] } ] }, "cleanupPeriodDays": 90 } - Headless-Gotcha: Im
-p-Modus bricht ein nicht durchpermissions.allowgedecktes Tool den Run ab (kein interaktiver Prompt). Dasallow-Set muss die für das Profil nötigen Tools explizit enthalten — dokumentieren im README und in jedem Profile-Template.
2.6 Secrets/Env-Handling
Was: sanitizedEnv() Funktion, die Secret-artige Variablen entfernt, bevor der claude-Prozess gestartet wird. Whitelist statt Blacklist wo möglich.
Warum: Siehe §4.5 der Analyse — sonst kann jeder Agent per echo $DISCORD_BOT_TOKEN den Bot-Token exfiltrieren.
Wie:
- Neue Datei:
src/runtime/env.ts, exportiertsanitizedEnv(extra?: Record<string, string>). - Entfernt:
DISCORD_BOT_TOKEN,DISCORD_CLIENT_SECRET,DISCORD_PUBLIC_KEY, alles matching/^(DISCLAW_SECRET_|SECRET_|TOKEN_|API_KEY_?)/i, plus eine konfigurierbare Block-Liste indisclaw.yaml. - Fügt hinzu:
CI=true,DISCLAW_AGENT=1,DISCLAW_AGENT_NAME=<name>.
2.9 Workspace-Location: Standardmäßig außerhalb des DisClaw-Repos
Was: Workspaces liegen per Default unter ~/.disclaw/workspaces/<agent>/ (Linux/Mac) bzw. %USERPROFILE%\.disclaw\workspaces\<agent>\ (Windows). Der Pfad ist über disclaw.yaml → workspaces_root konfigurierbar, der Default ist aber immer absolut außerhalb des DisClaw-Repo-Roots. Das bisherige ./workspaces/ im Repo wird nicht mehr als Default verwendet (nur noch als Opt-in für Development/Tests — dann mit Warning-Log beim Bot-Start).
Warum: Siehe cli-feature-answers.md Frage 8. Claude Code walkt beim Start parent directories hoch und konkateniert alle gefundenen CLAUDE.md-Dateien in den System-Prompt. Liegt der Agent-Workspace unter C:\Code\side\disclaw\workspaces\<agent>\, dann sieht Claude zwangsläufig auch:
C:\Code\side\disclaw\CLAUDE.md(DisClaw-Projekt-Identität)~/.claude/CLAUDE.md(User-Scope, developer-spezifisch)- ggf. weitere Parent-Scope-Dateien.
Das kontaminiert die Agent-Identität: Der Agent "weiß" plötzlich, dass er Teil von DisClaw ist, und erbt den Ton/Anweisungen der Developer-eigenen User-CLAUDE.md. Weder reproduzierbar noch sicher.
Zwei Verteidigungslinien, kombiniert:
- Strukturell: Workspaces liegen außerhalb des DisClaw-Repos. Dadurch existiert oberhalb keine Projekt-CLAUDE.md.
~/.claude/CLAUDE.mdbleibt ein Rest-Leak-Vektor. - Explizit: Alle CLI-Calls nutzen
--bare(skippt CLAUDE.md-Walk-Up, Hooks-Discovery, Skills, Plugins, MCP, Auto-Memory komplett) und die Agent-Identität wird über--append-system-prompt-file <ws>/CLAUDE.mdexplizit injiziert. Das ist reproduzierbar und zukunftssicher — laut Doku wird--bareohnehin der zukünftige Default für-p.
Konsequenz für bestehenden Code:
disclaw.yaml-Default:workspaces_root: "~/.disclaw/workspaces"(Tilde-Expansion +path.resolvegegenos.homedir()).src/config/loader.ts: Tilde-Expansion implementieren, absolute Pfade perpath.resolvenormalisieren.src/agent/runner.ts: Immer--bare+--append-system-prompt-filean den Call anhängen.src/commands/new-agent.ts: Workspace unter dem neuen Root anlegen, Channel-Name und DB-Einträge identisch wie vorher.- Migrationshinweis für bestehende Workspaces: Ein
/migrate-workspaces-Command (Phase 6+) oder manuelle Instruktion im README: Bestehende./workspaces/<agent>/-Ordner nach~/.disclaw/workspaces/<agent>/verschieben, DB-Spalteworkspace_pathperUPDATEanpassen, Channel-Mapping bleibt. Im MVP reicht ein dokumentierter manueller Schritt. - Dev-Opt-in: Wenn
workspaces_rootinnerhalb des DisClaw-Repos liegt (erkannt viapath.relative(repoRoot, wsRoot)startet nicht mit..), loggt der Bot beim Start eine Warnung und setzt zusätzlichclaudeMdExcludesim gerenderten.claude/settings.jsonmit absoluten Pfaden für die DisClaw-Projekt-CLAUDE.md und alle.claude/rules/**-Globs des Parents.
Quelle / Status: Bestätigt via cli-feature-answers.md Frage 8 mit Zitat aus code.claude.com/docs/en/memory. Konfidenz: hoch.
2.7 Logging und Error-Handling
Was: pino als strukturierter JSON-Logger. Child-Logger pro Komponente (bot, router, runner, commands.new-agent). Error-Handler-Strategie: Fehler werden geloggt (mit Stack), der Discord-Channel bekommt eine sanitized Nachricht (keine absoluten Pfade, keine Stack Traces).
Warum: console.log ist bei mehreren Agenten und paralleler Ausführung unlesbar. Pino ist schnell, JSON-first, und Standard in Node-Server-Apps. Pretty-Printing nur in dev via pino-pretty.
Wie:
- Neue Datei:
src/runtime/logger.ts, exportiertrootLoggerundchildLogger(component: string). - Sanitizer:
src/runtime/sanitize.ts, entfernt absolute Pfade (Regex aufROOT_DIR), Tokens, Home-Pfad.
2.8 Test-Strategie: Vitest, Unit-first
Was: vitest als Test-Runner. Unit-Tests für reine Funktionen (splitForDiscord, sanitizedEnv, parseClaudeJsonOutput, agent-name-validation, channel-queue). Integration-Tests mit gemockten Discord- und CLI-Clients. Keine E2E-Tests im MVP (zu fragil gegen echte Discord-API).
Warum: Vitest hat minimale Config, läuft nativ mit TypeScript via esbuild, und ist für diese Projektgröße angemessener als Jest.
Wie:
tests/unit/für reine Funktionen.tests/integration/für Router + Runner mit Mock-Spawner, Mock-DiscordChannel.- Mock-Strategie für
claudeCLI: Ein Fake-Spawner der einFake-Claude-Script aufruft (tests/fixtures/fake-claude.mjs), das deterministische JSON-Antworten ausgibt. So testet man die echten spawn-Pfade, ohne von einer echten Claude-Installation abhängig zu sein. - Discord:
@discordjs/core-Mocks oder manuell via Interface-Abstraktion — siehe §5 "Discord-Adapter".
3. Tools und Libraries
Wird verwendet
| Paket | Version | Zweck | Begründung |
|---|---|---|---|
discord.js |
^14.16.0 | Discord-Client | Schon drin, de-facto Standard. |
better-sqlite3 |
^11.0.0 | SQLite-DB | Schon drin, synchrone API passt zum Handler-Modell. |
dotenv |
^16.4.0 | .env-Loader |
Schon drin. |
yaml |
^2.6.0 | YAML-Parser für disclaw.yaml und agent.yaml |
Schon drin. |
zod |
^3.23 | Runtime-Validierung für Config, CLI-JSON-Output, Slash-Command-Args | Sichert Grenzen zwischen untrusted Input und Code. Klein, ohne Runtime-Abhängigkeiten. |
cross-spawn |
^7 | Plattformübergreifender child_process.spawn-Ersatz |
Löst Windows-.cmd-Wrapper korrekt auf (claude.cmd), quoted Windows-Args sicher, umgeht CVE-2024-27980 (Node 20.12+). Ohne Shell, also kein Command-Injection-Risiko. ~30 kB, keine transitiven Deps. Siehe §2.2. |
@types/cross-spawn |
^6 | Typdefinitionen | devDep. |
pino |
^9 | Strukturiertes Logging | Schnell, JSON-first, Child-Logger. |
pino-pretty |
^11 | Dev-only Pretty-Printer | Nur in devDependencies. |
vitest |
^2 | Test-Runner | Native TS, schnell, minimal Config. |
@types/node |
^22 | Typdefinitionen | Schon drin. |
typescript |
^5.7 | Compiler | Schon drin. |
Wird explizit NICHT verwendet
- Kein ORM (Prisma/Drizzle): Overkill für 2 Tabellen.
better-sqlite3-Prepared-Statements sind schnell und typisiert genug via manuelle Interfaces. - Kein Express/Fastify: Kein HTTP-Endpoint im MVP. Wenn später ein Web-Dashboard dazu kommt, Fastify.
- Kein Redis/BullMQ: Single-Process-Queue reicht. Externe Queues erst bei Multi-Instance-Deployment.
- Kein
discord-markdowno. ä.: Splitting ist simpel genug für Eigenbau und genauer kontrollierbar. - Kein Jest: Vitest ist leichter, schneller, direkt TS-fähig.
- Kein
@anthropic-ai/sdkoderclaude-agent-sdk: ADR-001 sagt CLI-only. - Kein
winston: Pino ist schneller und hat bessere Child-Logger-Semantik.
4. Phasenplan
Jede Phase ist in sich lauffähig. Abhängigkeiten sind explizit genannt. Nummerierung ist nicht chronologisch, sondern logisch.
Phase 0 — Härtung (P0, blockiert jede Live-Nutzung)
Ziele: Sicherheitslücken schließen, bevor irgendein echter Discord-Server angeschlossen wird.
Tasks:
src/agent/runner.ts:shell: trueentfernen; stattdessencross-spawnverwenden (siehe §2.2). Neue Helper-Dateisrc/runtime/resolve-claude.ts: liestCLAUDE_PATHaus.envoder führt einmaligwhere claude/which claudeaus, cached das Ergebnis in-memory, wirft eine klare Fehlermeldung bei Nichtfund.src/runtime/env.ts(neu):sanitizedEnv()wie in §2.6. Inrunner.tsstatt...process.envverwenden.- Workspace-Root-Umstellung (§2.9):
disclaw.yaml-Default auf~/.disclaw/workspacesumstellen.src/config/loader.ts: Tilde-Expansion +path.resolve.src/commands/new-agent.ts: Workspace unter dem neuen Root anlegen. Warning-Log, wenn der konfigurierte Root innerhalb des DisClaw-Repos liegt. Manueller Migrationshinweis für bestehende./workspaces/<agent>/-Ordner im README. src/commands/new-agent.ts: Name-Regex behalten, zusätzlich Path-Containment-Check:const root = path.resolve(config.workspaces_root); const wsPath = path.resolve(root, name); if (!wsPath.startsWith(root + path.sep)) throw new Error("Path traversal");src/agent/identity.ts:.claude/settings.json-Template härten — Schema wie in §2.5 (mitpermissions.allow/ask/deny/defaultModeund dreistufigemhooks-Block). Deny für../**, Deny-Patterns für gefährliche Bash-Kommandos.src/agent/identity.ts:CLAUDE.md-Template bekommt die Prompt-Injection-Resistenz-Klausel (Punkt 5 aus §3 der Analyse).src/agent/runner.ts: CLI-Call immer mit--bare --append-system-prompt-file <ws>/CLAUDE.mdaufbauen (siehe §2.9), damit Parent-CLAUDE.md-Leaks strukturell ausgeschlossen sind — auch für Dev-Mode-Workspaces innerhalb des Repos.tests/unit/sanitize-env.test.ts,tests/unit/path-traversal.test.ts,tests/unit/resolve-claude.test.ts(mocked),tests/unit/workspace-root-resolve.test.ts(Tilde-Expansion, Warning bei Repo-internem Pfad).
Definition of Done:
- Kein
shell: truemehr im Code; Spawning läuft übercross-spawnmit resolvtem Claude-Pfad. - Ein Test demonstriert, dass
DISCORD_BOT_TOKENnicht im Child-Env landet. - Ein Test demonstriert, dass
name: "../etc"als Slash-Command-Input abgewiesen wird. .claude/settings.jsonin einem frisch erstellten Workspace enthält die Deny-Liste und das korrektehooks.PreToolUse[].matcher-Layout.CLAUDE.mdenthält den Prompt-Injection-Resistenz-Satz.- Default-
workspaces_rootzeigt auf~/.disclaw/workspaces, Tilde wird korrekt expandiert, Warning bei repo-internem Pfad. - CLI-Calls enthalten
--bareund--append-system-prompt-file <ws>/CLAUDE.md; ein Test (mit Fake-Claude) verifiziert, dass kein Parent-CLAUDE.md-Leak auftritt.
Abhängigkeiten: Keine. Erste Phase.
Phase 1 — Solides MVP
Ziele: Ein Agent antwortet zuverlässig auf Discord, mehrfache Messages im selben Channel werden serialisiert, Fehler sind verständlich, Logs sind strukturiert, Grundstruktur ist testbar.
Tasks:
src/runtime/logger.ts(neu): Pino-Setup,rootLogger,childLogger(component). Inindex.ts,bot.ts,router.ts,runner.ts,commands/new-agent.tsconsole.*ersetzen.src/runtime/sanitize.ts(neu):sanitizeForDiscord(text, rootDir)— entfernt Abspfade.src/runtime/channel-queue.ts(neu):ChannelQueue-Klasse wie in §2.3. Inrouter.tsintegrieren.src/runtime/concurrency.ts(neu):Semaphore(max: number)-Klasse. Globaler Cap ausdisclaw.yaml→max_concurrent_agents: 4.src/discord/split.ts(neu):splitForDiscord()wie in §2.4.router.tsverwendet ihn.src/agent/runner.ts: API-Refactoring aufRunResult = { text: string, sessionId?: string, toolCalls?: ToolCall[] }. Return-Type vorbereiten für spätere Streaming-Unterstützung (runAgent()gibt heutePromise<RunResult>, später zusätzlichrunAgentStream()).src/runtime/zod-schemas.ts(neu): Zod-Schemas fürdisclaw.yaml,agent.yaml, Claude-CLI-JSON-Output.parseClaudeJsonOutput()validiert via Zod.src/config/loader.ts: Validierung über Zod stattasCast.src/db/database.ts: Schema-Inline-String entfernen, stattdessensrc/db/schema.sqlladen (Single Source of Truth).tests/unit/split-for-discord.test.ts(Fences, lange Zeilen, Fallback),tests/unit/channel-queue.test.ts,tests/unit/parse-claude-output.test.ts.tests/integration/router.test.ts: mockt DB, DiscordChannel (Interface), Spawner — verifiziert das End-to-End-Verhalten bei parallelen Nachrichten (Reihenfolge, keine Race)..env.examplehinzufügen.package.json:test-Script,devDependenciesfür vitest, pino-pretty, zod, pino.docs/cli-feature-probe.md(neu): Manuelles Regression-Script, das die kritischen Annahmen auscli-feature-answers.md(Session-ID top-level,usage-Block-Pfade, stream-json-Event-Typen, Session-Slug-Regel) gegen die lokal installierte CLI verifiziert und Resultate dokumentiert. Läuft als Sanity-Check bei CLI-Upgrades.
Definition of Done:
- Bot startet, erstellt Management-Channel, akzeptiert
/new-agent, antwortet im Agent-Channel korrekt auf eine Nachricht. - Drei schnell hintereinander gesendete Nachrichten im gleichen Channel werden in exakter Reihenfolge verarbeitet (verifiziert im Integration-Test).
- Log-Output ist strukturiertes JSON (mit
pino-prettyin dev). npm testläuft grün, coverage fürsplit,channel-queue,sanitize-env,parse-claude-output,router.- Kein
console.logmehr im Produktionscode (nur in Tests/Probe-Scripts erlaubt).
Abhängigkeiten: Phase 0.
Phase 2 — Context-Effizienz (--resume) — FREIGEGEBEN
Ziele: Prompt-Cache-Hits, drastisch gesenkte Input-Tokens, saubere Historie-Semantik.
Status: Freigegeben durch cli-feature-answers.md Frage 1+2. -p + --resume ist dokumentiert, session_id liegt top-level im JSON-Output, Session-ID bleibt beim Resume stabil (es sei denn --fork-session).
Tasks:
- DB-Migration:
ALTER TABLE workspaces ADD COLUMN claude_session_id TEXT; ALTER TABLE workspaces ADD COLUMN session_updated_at TEXT;. Inschema.sqlpflegen, indatabase.tsals Migration-Step addieren (simplertry { ALTER } catch {}-Block reicht). src/db/database.ts: Neue MethodensetSession(workspaceId, sessionId),getSession(workspaceId),clearSession(workspaceId).setSessionaktualisiert auchsession_updated_atmit aktuellem ISO-Timestamp.src/agent/runner.ts: Session-Flow wie in §2.1. Nach jedem erfolgreichen Runstore.set(ws.id, json.session_id)aufrufen — auch wenn die ID gleich geblieben ist (defensive Update-after-run). Fallback-Pfad implementieren: Bei--resume-Fehlerstore.clear+ Retry ohne--resume(optional--continueals Zwischenstufe).- Die History-Freitext-Injektion nur noch als Cold-Start-Rehydrierung verwenden (strukturierter als heute, mit klaren
<turn role="user|assistant">Blocks). tests/integration/session-resume.test.ts: Fake-Claude gibt beim ersten Call einesession_idtop-level zurück, der zweite Call muss diese via--resume <id>wieder verwenden. Fehlschlag simulieren → Fallback-Pfad wird getroffen. Edge-Case: Fake-Claude gibt beim zweiten Call eine neuesession_idzurück (Fork-Szenario) → DB muss auf neue ID updaten.- Metrik-Logging:
usage-Block aus JSON-Output parsen. Die exakten Pfade (result.usage.cache_read_input_tokensvs. top-levelusage.cache_read_input_tokens) werden im Probe-Script empirisch verifiziert und dann hartcodiert. Log-Line:log.info({ input_tokens, cache_read, cache_create, output_tokens }, "claude_run"). - Optional (Nice-to-have): Neue Tabelle
agent_runs(workspace_id, ts, input_tokens, cache_read, cache_create, output_tokens, duration_ms)für Pro-Agent-Metriken.
Definition of Done:
- Zweite Nachricht im gleichen Channel verwendet dieselbe
session_id(verifiziert via Fake-Claude). - Session-ID überlebt Bot-Neustart.
- Bei bewusstem Löschen der Session-Datei reagiert der Runner mit automatischem Fallback, speichert neue Session-ID, keine User-sichtbare Fehlermeldung.
session_idwird nach jedem Run in der DB geschrieben, auch wenn identisch.- Log-Line zeigt gecachte Tokens.
Abhängigkeiten: Phase 0, Phase 1.
Phase 3 — UX-Feinschliff
Ziele: Discord-Ausgabe fühlt sich gut an. Lange Antworten, Codeblöcke, Attachments, Typing-Refresh.
Tasks:
src/router.ts: Typing-Refresh-Intervall auf 8 s. Typing wird nur so lange ausgesendet, wie derrunAgent-Call läuft.src/discord/send-response.ts(neu):sendResponse(channel, text, { asAttachmentAfter = 8000 }). Wenn Text > Schwelle → als.md-Attachment senden.- Attachment-Support eingehend:
router.tsprüftmessage.attachments. Download inworkspaces/<agent>/.disclaw-inbox/<ts>-<name>.ext. Im Prompt referenziert. Nach erfolgreicher Antwort: TTL-Cleanup-Task. - Input-Limit: User-Nachricht > 4000 Zeichen → Reaction
⚠️und Hinweis im Channel. - Reaction
👀sobald Message in Queue landet;✅bei Erfolg;❌bei Fehler. Alle.catch(() => {}), nicht blockierend. tests/unit/send-response.test.ts,tests/unit/attachment-inbox.test.ts(mit tmp-Dir).
Definition of Done:
- Lange Antworten werden korrekt gesplittet ohne kaputte Fences.
- Sehr lange Antworten werden als
.md-File gesendet. - Bilder aus Discord landen im Workspace und Claude kann sie via Read-Tool sehen.
- Typing-Indikator bleibt die ganze Laufzeit sichtbar, ohne "pausen".
Abhängigkeiten: Phase 1.
Phase 4 — Skills und Permissions (Profile)
Ziele: Agents sind pro Rolle sinnvoll voreingestellt. Hook-Guard ist aktiv. Audit-Log läuft.
Tasks:
src/agent/profiles/(neu): Ordner mitresearcher.md,developer.md,writer.md,ops.md,sandboxed.mdalsCLAUDE.md-Templates und*.settings.jsonals.claude/settings.json-Templates. Platzhalter{{name}},{{role}},{{workspace_abs}}.src/agent/profiles/index.ts:ProfileRegistry— maps name →{ claudeMd: Template, settings: Template, hooks: Template[] }.src/commands/new-agent.ts: Neuer Option-Parameterprofilemit Choices. Default =developer.src/agent/identity.ts:setupAgentWorkspace()nimmtprofileund rendert Templates aus der Registry.src/agent/hooks/guard-tool.cjs(generiert beim Create): Node-Script, das als PreToolUse-Hook jeden Tool-Call gegen den Workspace-Pfad prüft, außerhalb → exit 2 mit Block-Reason. Jeder Call wird zusätzlich inworkspaces/<agent>/.disclaw-audit.jsonlgeschrieben. Event-Typen gemäßcli-feature-answers.mdFrage 3:PreToolUse,PostToolUse,UserPromptSubmit,SessionStart,Stop. Wir nutzen primärPreToolUsemitmatcher: "Bash|Read|Write|Edit"..claude/settings.json-Template referenziert den Hook im dreistufigen Layout (hooks.PreToolUse[].hooks[].{type, command, timeout}), siehe §2.5.tests/unit/profile-registry.test.ts,tests/integration/hook-guard.test.ts(mit Fake-Claude, der einen bösartigen Tool-Call simuliert).
Definition of Done:
/new-agent name:r profile:researchererstellt einen Agenten mit read-only-Permissions.- Ein Hook-Test verifiziert, dass
Bash(cat ../../other/secret.txt)abgewiesen und im Audit-Log protokolliert wird. - Alle fünf Profile sind vorhanden und passen zu ihrer §3-Tabelle der Analyse.
Abhängigkeiten: Phase 0 (Settings-Template). Settings-Schema ist durch cli-feature-answers.md Frage 3 verifiziert.
Phase 5 — Streaming
Ziele: Antworten erscheinen inkrementell im Discord-Channel.
Tasks:
src/agent/runner.ts: Zweiter ExportrunAgentStream(): AsyncIterable<StreamEvent>. Intern:claude -p "<msg>" --output-format stream-json --verbose --include-partial-messages --bare --append-system-prompt-file <ws>/CLAUDE.md(plus--resumewie in Phase 2). Die Flags--verboseund--include-partial-messagessind lautcli-feature-answers.mdFrage 4 zwingend, sonst emittiert die CLI nur das Final-result-Event ohne Text-Deltas.- NDJSON-Parser: Zeilenweise
JSON.parse. Dispatch nach.type:system(init/api_retry, skip),assistant(Text- und Tool-Use-Blocks im Native-API-Format),user(Tool-Results),stream_event(partial deltas — interessant ist.event.delta.type === "text_delta"mit.event.delta.text),result(Final-Event, enthält session_id und usage). Parser defensiv gegen unbekannte Typen (skip statt crash) — CLI-Updates können neue Subtypes einführen. src/discord/stream-to-discord.ts(neu): Nimmt einenAsyncIterable<StreamEvent>, akkumuliert Text-Deltas, editiert die Bot-Message alle ~1200 ms (Discord-Rate-Limit), rolled auf neue Message bei >1900 Zeichen. Beiresult-Event: finalen Stand commitet.router.ts: Feature-Flagstreaming: true/falseausdisclaw.yaml. Fallback auf Bulk-Mode bei Fehlern.tests/integration/streaming.test.tsmit Fake-Claude, der ein NDJSON-Scripted-Log auf stdout schreibt (system → assistant → mehrere stream_events mit text_deltas → result).
Definition of Done:
- Streaming ist per Flag aktivierbar und funktioniert End-to-End.
- Edits respektieren Discord-Rate-Limits (min. 1200 ms zwischen Edits).
- Bei Fehler im Stream wird die Message sauber finalisiert.
Abhängigkeiten: Phase 1 (Runner-API vorbereitet). Phase 2 (Session-Resume bleibt auch im Stream-Modus aktiv).
Phase 6+ — Zukunft
Klar: nicht jetzt planen, aber Pfade offenhalten.
/list-agents,/delete-agent,/agent-config: Command-Handler-Interface aus §5 macht das zu einer 30-Minuten-Sache pro Command./delete-agentmuss Channel + Workspace + DB-Einträge +~/.claude/projects/<hash>/-Session atomar löschen.- User-Ratelimit: Token-Bucket in
src/runtime/ratelimit.ts, Map<userId, Bucket>. - Daily-Budget pro Agent: DB-Tabelle
agent_usage(workspace_id, day, runs), Check vor jedem Run. - Agent-zu-Agent: Ein Agent kann eine Nachricht an einen anderen Agent-Channel adressieren → Router erkennt und delegiert.
- Docker-Isolation:
DockerRunnerals zweite Implementierung desAgentEngine-Interface (§5). - Web-Dashboard: Fastify-Server, der
DisclawDatabaseteilt. - Alternative AI-Engine: Ein
ClaudeSdkEnginegegenclaude-agent-sdkfür Nutzer ohne Claude-Pro-Abo. GleichesAgentEngine-Interface.
5. Erweiterbarkeit (Fokus!)
Der Code ist so zu strukturieren, dass neue Features ohne Core-Änderungen andocken. Kernmechanismen:
5.1 Schichtentrennung
src/
bot.ts Discord-Adapter (Event → Router)
router.ts Routing + Queue + Sanitize (kennt Runner abstrakt)
discord/ Alles Discord-nahe: split, send, stream-to-discord
agent/
engine.ts AgentEngine-Interface (siehe 5.6)
runner.ts ClaudeCliEngine: implements AgentEngine
identity.ts Workspace-Create (delegiert an Profile)
profiles/ Templates pro Profil
hooks/ Pre/Post-Hook-Scripts
commands/
index.ts CommandRegistry (siehe 5.2)
new-agent.ts
list-agents.ts (später)
delete-agent.ts (später)
runtime/
channel-queue.ts
concurrency.ts
env.ts
logger.ts
sanitize.ts
ratelimit.ts (später)
db/
database.ts Repository-Stil, keine Business-Logik
schema.sql
config/
loader.ts
Regel: runtime/, db/, discord/ sind Leaf-Module, kennen sich nicht gegenseitig über die Grenzen hinweg. agent/ kennt runtime/ und db/-Typen, aber nicht discord.js. router.ts ist der einzige Ort, der Discord und Agent zusammenbringt.
5.2 Plugin-Interface für Slash-Commands
Neues Interface, das neue Commands an einer Stelle registriert:
// src/commands/types.ts
import { SlashCommandBuilder, ChatInputCommandInteraction } from "discord.js";
import { DisclawDatabase } from "../db/database";
import { DisclawConfig } from "../config/loader";
export interface CommandContext {
db: DisclawDatabase;
config: DisclawConfig;
managementChannelId: string | null;
}
export interface DisclawCommand {
definition: SlashCommandBuilder;
handle(interaction: ChatInputCommandInteraction, ctx: CommandContext): Promise<void>;
}
// src/commands/index.ts
import { newAgentCommand } from "./new-agent";
// import { listAgentsCommand } from "./list-agents";
export const commands: DisclawCommand[] = [
newAgentCommand,
// listAgentsCommand,
];
bot.ts registriert alle Commands in einer Schleife und dispatched via commandName-Lookup. Neuer Command = neue Datei + ein Array-Eintrag. Keine Änderung an bot.ts nötig.
5.3 Profile-Templates als Registry
// src/agent/profiles/index.ts
export interface ProfileTemplate {
name: string;
description: string;
renderClaudeMd(ctx: { name: string; role: string }): string;
renderSettings(ctx: { workspaceAbs: string; name: string }): Record<string, unknown>;
hooks?: HookDefinition[];
}
export const profiles: Record<string, ProfileTemplate> = {
developer: developerProfile,
researcher: researcherProfile,
writer: writerProfile,
ops: opsProfile,
sandboxed: sandboxedProfile,
};
Ein neuer Profil-Typ (z. B. data-scientist) ist eine neue Datei + ein Registry-Eintrag. identity.ts muss nicht angefasst werden.
5.4 Hook-System (Pre/Post Message, Pre/Post Spawn)
// src/agent/hooks.ts
export type HookEvent =
| { type: "preMessage"; workspace: Workspace; message: string }
| { type: "postMessage"; workspace: Workspace; response: string }
| { type: "preSpawn"; workspace: Workspace; args: string[] }
| { type: "postSpawn"; workspace: Workspace; result: RunResult };
export type Hook = (event: HookEvent) => Promise<void | { cancel?: boolean; reason?: string }>;
export class HookBus {
private hooks: Hook[] = [];
register(h: Hook) { this.hooks.push(h); }
async emit(e: HookEvent): Promise<{ cancel: boolean; reason?: string }> { ... }
}
router.ts und runner.ts emittieren an einen global injizierten HookBus. Beispiel-Hooks: Audit-Logger, Ratelimit, Daily-Budget, Metriken. Alle außerhalb des Core-Codes, einfach pluggable.
Wichtig: Die .claude/-Hooks (PreToolUse etc.) sind ein anderer Mechanismus — die werden von Claude Code selbst gefeuert. Das HookBus hier ist DisClaw-intern.
5.5 MCP-Server pro Workspace (vorbereitet, später aktiviert)
Das Profile-Template kann optional eine mcpServers-Sektion in die .claude/settings.json rendern. Die Liste der verfügbaren MCPs ist in src/agent/mcp/registry.ts zentral gepflegt. Aktivierung pro Profil deklarativ:
developerProfile.mcpServers = ["git"];
opsProfile.mcpServers = ["docker", "k8s"];
Der Renderer fügt die korrekten Stanzas in settings.json ein. Kein Code-Change im Runner nötig.
5.6 Abstraktion der AI-Engine
// src/agent/engine.ts
export interface AgentEngine {
run(opts: RunOptions): Promise<RunResult>;
runStream?(opts: RunOptions): AsyncIterable<StreamEvent>;
}
ClaudeCliEngine ist heute die einzige Implementierung. Später könnten ClaudeSdkEngine (direkte API) oder DockerClaudeCliEngine (CLI in Container) als Drop-in-Alternativen dazu kommen. Der Router kennt nur das Interface.
5.7 Discord-Adapter-Abstraktion (für Testbarkeit)
Alle Discord-Interaktionen im Router gehen über ein kleines Interface ChannelSink:
export interface ChannelSink {
send(text: string): Promise<{ id: string }>;
edit(messageId: string, text: string): Promise<void>;
sendTyping(): Promise<void>;
sendFile(name: string, content: Buffer): Promise<void>;
react(messageId: string, emoji: string): Promise<void>;
}
In Produktion wraps das einen TextChannel. In Tests ist es ein Mock. So sind Router-Tests komplett ohne discord.js möglich.
6. Offene Fragen an den AI Engineer
Die ursprünglichen 10 Fragen wurden am 2026-04-08 vom AI Engineer beantwortet. Siehe docs/cli-feature-answers.md für Volltext, Quellen und Konfidenzangaben.
Verbleibende Rest-Unsicherheiten (alle empirisch via Probe-Script zu bestätigen, kein Blocker für Phase-Start):
-
Exakte JSON-Pfade des
usage-Blocks (result.usage.cache_read_input_tokensvs. top-levelusage.cache_read_input_tokens): Die Existenz der Felder ist durch das API-Layer garantiert, aber der Output-Shape der CLI wird in Phase 2 per Probe-Script empirisch verifiziert und dann im Parser hartcodiert. -
stream-jsonEvent-Schema-Stabilität: Die groben Typen (system,assistant,user,stream_event,result) sind dokumentiert, aber Subtypes und Feld-Layouts können zwischen CLI-Versionen driften. Mitigation: Parser defensiv (skip unknown), Regression-Test im Probe-Script. -
Session-Slug-Transformation auf Linux/Mac: Windows-Slug-Regel (
C:\Code\side\disclaw→C--Code-side-disclaw) ist empirisch bestätigt, Linux-Regel (/home/nick/...→-home-nick-...) ist aus Analogie abgeleitet. Muss auf Linux-Runner geprüft werden, sobald/delete-agentimplementiert wird (Phase 6+). -
Multimodal-Passthrough ohne Tool-Round-Trip:
@./path/image.pngim Prompt funktioniert via Read-Tool + Vision; ein direkter Vision-Input-Block ohne Round-Trip existiert im CLI--p-Modus nicht und würde eine Migration zum Agent SDK erfordern. Scope-Entscheidung: bleibt Phase 6+.
7. Risiken und Mitigation
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|---|---|---|---|
--resume funktioniert nicht wie erwartet in der installierten CLI |
Niedrig | Hoch (Phase 2 blockiert) | Per cli-feature-answers.md Frage 1+2 offiziell dokumentiert und bestätigt. Probe-Script als Regression-Check bleibt. Fallback-Pfad (DB-Rehydrierung + --continue) bleibt als Degradation verfügbar. |
CLAUDE.md-Walk-Up kontaminiert Agenten-Identität (Parent-Projekt-CLAUDE.md oder ~/.claude/CLAUDE.md leaked in System-Prompt) |
Mittel (strukturell gegeben, wenn nicht mitigiert) | Hoch (Reproduzierbarkeit, Sicherheit, Identitäts-Leak) | §2.9: Workspaces außerhalb des DisClaw-Repos (~/.disclaw/workspaces/) plus --bare + --append-system-prompt-file bei jedem Call. Integration-Test mit Fake-Claude, der den empfangenen System-Prompt echoed, und Assertion, dass ausschließlich die Workspace-CLAUDE.md drin ist. Für Dev-Mode-Workspaces innerhalb des Repos zusätzlich claudeMdExcludes im Settings. |
Windows-Spawn schlägt auf frischer Node-20.12+-Installation fehl (claude.cmd, CVE-2024-27980) |
Mittel (default), niedrig (nach Phase 0) | Hoch (DisClaw startet auf Windows gar nicht) | §2.2: cross-spawn als Dependency, where claude-Lookup mit absolutem Pfad-Cache, klare Fehlermeldung wenn nicht auffindbar. CI-Test auf Windows-Runner (GitHub Actions windows-latest) verifiziert Smoke-Test-Spawn. |
| Command Injection bleibt unentdeckt in einem Codepfad | Niedrig (nach Phase 0) | Kritisch | shell: false als Lint-Regel im CI. Code-Review-Checkliste. Integration-Test mit bösartigem Input. |
| Prompt-Injection bricht Permissions aus | Mittel | Hoch | Defense in depth: CLAUDE.md-Klausel, settings.json deny, PreToolUse-Hook, Audit-Log. Keine davon allein reicht. |
.claude/settings.json-Schema ändert sich zwischen CLI-Versionen |
Mittel | Mittel | Schema-Version beim Workspace-Create detektieren (claude --version parsen), Template pro Release pflegen. Warnung bei unbekannter Version. |
| Parallele Nachrichten im selben Channel korrumpieren Workspace-Dateien | Hoch (ohne Queue) | Hoch | Per-Channel-Queue in Phase 1. Integration-Test. |
Secret-Exfiltration via echo $DISCORD_BOT_TOKEN |
Hoch (aktuell) | Kritisch | sanitizedEnv() in Phase 0. Test, der den resultierenden Env-Set prüft. |
| Discord-Rate-Limits bei Streaming | Niedrig | Mittel | Min. 1200 ms zwischen Edits, neue Message bei Überlauf. discord.js 429-Handling nutzen. |
| Speicher-Leaks bei Long-Running-Bot-Prozess | Niedrig | Mittel | Pino-Logs + process.memoryUsage() alle 5 min als Debug-Log. channelQueues-Map räumt sich selbst auf. |
| Zwei DisClaw-Instanzen gegen dieselbe DB/Workspace | Niedrig | Hoch | Lock-File im Workspaces-Root beim Start. Fehlschlag, wenn Lock existiert und PID noch läuft. |
Benutzer editieren CLAUDE.md manuell mit Injection |
Mittel | Niedrig (der Benutzer "schadet" sich selbst) | Dokumentiert, kein technischer Fix. |
| PreToolUse-Hook-Script ist fehlerhaft und blockt legitime Calls | Mittel | Mittel | Hook schreibt Audit-Log auch bei Block; /reset-permissions-Command später; im MVP manuelles Editieren der Settings. |
Test-Suite wird träge, wenn echte claude CLI involviert ist |
Mittel | Niedrig | Unit-Tests nutzen Fake-Spawner. Echte-CLI-Tests nur in tests/probe/ und manuell. |