Übersicht
Dieses Board war von Anfang an auch ein Lernprojekt: wie steuert man einen
headless claude -p-Prozess zuverlässig, und wie verhindert man,
dass er etwas Falsches tut? Die folgenden vier Begriffe - Harness,
Settings, Hooks und Guardrails -
kommen alle aus dem Claude-Code-Vokabular. Diese Seite zeigt zu jedem Begriff kurz,
was er allgemein bedeutet, und direkt danach, wo genau er in diesem
Projekt umgesetzt ist (Datei, Funktion, Grund).
1. Harness - das Gerüst um die KI herum
Die "Harness" ist der Code, der die KI überhaupt erst gezielt startet, ihr eine Aufgabe gibt, ihre Antwort einsammelt und entscheidet, was als Nächstes passiert. Die KI selbst führt nichts von sich aus aus - sie antwortet nur auf einen Aufruf.
So im Worker (worker/main.py):
-
run_claude()ist der zentrale Wrapper um jedenclaude -p- Aufruf: Prompt geht perstdinrein (nicht als Kommandozeilen-Argument, damit Freitext aus Tickets kein Escaping-Problem auslöst), die Antwort kommt als--output-format jsonzurück, inkl. Token-Zahlen und Session-ID. -
Ein Ticket läuft nicht in einem KI-Aufruf, sondern als mehrstufige
Pipeline: optionaler Vorab-Check (günstiges Modell, prüft ob die Aufgabe
überhaupt klar genug ist) → PO-Verfeinerung (destilliert Akzeptanzkriterien, siehe
run_refine_stage()) → eigentliche Implementierung (teureres Modell, vollen Repo-Zugriff) → unabhängiger Testing-Agent (run_testing_stage()) → bei Ablehnung bis zu zwei Korrekturrunden (run_fix_round(), per--resumean dieselbe Session angehängt, damit der Kontext erhalten bleibt). -
Die Harness entscheidet außerdem, welches Modell für welche Stufe läuft
(günstiges Modell für Vorab-Check/Verfeinerung/Testing, teureres für die eigentliche
Implementierung, siehe
worker/config.yaml) und meldet jeden Lauf (Dauer, Tokens, Kosten) perreport_run()ans Board zurück - das ist reine Harness-Buchhaltung, die KI selbst "weiß" davon nichts. -
Audit-Trail pro Lauf (seit 2026-10-23):
--output-format jsonliefert nur das Endergebnis - einPostToolUse-Hook (audit_hook.py) protokolliert deshalb zusätzlich JEDEN Tool-Call in eine pro-Lauf-Datei, dierun_claude()danach ausliest und auf die letzten 150 Schritte kappt. Macht sichtbar was der Agent Schritt für Schritt tat (aufklappbar in der Verbrauch-Tabelle am Ticket), nicht nur was am Ende rauskam. -
Plan-Review-Stufe (seit 2026-10-23, standardmäßig AUS -
config.yaml: plan_review_enabled): ein zusätzlicher read-only Lauf zwischen Vorab-Check und Implementierung liefert einen Umsetzungsplan + Risikoeinschätzung. Bewusste Abweichung von der ursprünglichen Roadmap-Idee: statt--permission-mode plan(Claude Codes interaktiver Plan Mode, der normalerweise auf eine menschliche Bestätigung wartet - im headless-p-Betrieb ohne verlässlich prüfbares Verhalten) nutztrun_plan_stage()dieselbe Allow-Liste wie der Testing-Agent (Read/Grep/Glob, Abschnitt 2) - schon getestete Infrastruktur statt eines ungetesteten CLI-Flags in einem Live-System. Bei echten Risikomerkmalen (Datenmodell, Deploy-relevante Dateien, Sicherheit, sichtbares Verhalten einer live laufenden Funktion) geht das Ticket über den bestehendenklaerung_noetig-Mechanismus zur Freigabe ans Board - keine neue Infrastruktur nötig. Bewusst fail-open (anders als der Testing-Agent): Timeout/kaputtes JSON blockiert die Implementierung nicht, die Stufe ist eine Transparenz-Ergänzung, kein hartes Gate.
2. Settings - wie ein Lauf konfiguriert wird
"Settings" sind die Stellschrauben, mit denen man das Verhalten von Claude Code steuert, ohne den Prompt selbst zu ändern - z.B. welche Hooks aktiv sind, welche Tools erlaubt sind, oder wie die Status-Anzeige aussieht.
Drei getrennte Ebenen in diesem Projekt:
-
Pro-Lauf-Settings-Datei:
guards.build_settings_file()schreibt vor jedemclaude -p-Aufruf eine frischeworker/state/guard_settings.jsonund hängt sie per--settingsein. Datei statt Inline-JSON, weilshell=Trueunter Windows Anführungszeichen in der Kommandozeile sonst zerlegt. Ihr einziger Inhalt aktuell: der Push-Block-Hook (siehe Abschnitt 3). -
Board-weite Settings (seit 2026-10-19, "⚙ Einstellungen" in der
Übersicht): Dinge wie
max_concurrent_ticketsoder die Usage-Gate- Schwellen liegen in dersettings-Tabelle der Board-Datenbank (server/src/routes/settings.js) und werden bei jedem Worker-Lauf perGET /api/settingsfrisch abgefragt - kein Neustart des Workers nötig, wenn Sebastian im Browser etwas ändert.worker/config.yamlist nur noch der Fallback, falls das Board gerade nicht erreichbar ist. -
Globale Claude-Code-Settings (
~/.claude/settings.jsonauf dem Worker-Rechner): einestatusLine, die bei jeder interaktiven/ Hintergrund-Session den aktuellenrate_limits-Stand inworker/state/rate_limits.jsonschreibt (worker/statusline.py) - Grundlage für das Usage-Gate (Abschnitt 4). Ein reinerclaude -p-Lauf löst keine statusLine aus, deshalb stößt das Gate selbst kurz eineclaude --bg-Hintergrundsession an, um einen frischen Stand zu erzwingen.
3. Hooks - technische Sperren statt Bitten im Prompt
Ein Hook ist ein eigenes kleines Programm, das Claude Code bei einem bestimmten
Ereignis aufruft - z.B. bevor ein Tool ausgeführt wird (PreToolUse) -
und das per Exit-Code entscheiden kann, den Aufruf durchzulassen oder zu blocken.
Der Unterschied zu "im Prompt schreiben, dass die KI etwas nicht tun soll": ein Hook
wirkt unabhängig davon, ob die KI die Anweisung befolgen würde oder nicht.
So im Worker:
-
worker/guard_hooks.pyist einPreToolUse-Hook aufBash-Aufrufe: eine Musterliste aus Regexes (git push, SSH/SCP,rmrekursiv+force, direkte Docker-Container-Eingriffe, Skripte percurl/wgetin eine Shell gepiped,sudo- siehe Abschnitt 4) prüft jeden Befehl, bewusst eng genug gefasst, dass z.B.git commit -m "fix push button"oder "fix sudo typo in docs" als Commit-Message nicht triggern. Bei einem Treffer gibt der Hook Exit-Code 2 zurück - das lehnt den Tool-Aufruf technisch ab, die Meldung geht als Fehlertext an Claude zurück. -
Eingehängt wird er über genau die Settings-Datei aus Abschnitt 2
(
guards.build_settings_file()), bei jedemclaude -p-Aufruf des Workers, egal in welcher Pipeline-Stufe. -
Entscheidend: der Hook greift auch unter
--permission-mode bypassPermissions, das der Worker braucht, damit die headless Session nicht bei jedem Dateizugriff auf eine (nicht vorhandene) interaktive Bestätigung wartet. Ohne den Hook wärebypassPermissions+ "bitte nicht pushen" im Prompt reine Hoffnung. -
Lifecycle-Hooks (seit 2026-10-23) - bisher nutzte der Worker nur
PreToolUse/PostToolUse(tool-bezogene Events). Neu:lifecycle_hooks.pyhängt zwei Session-Events ein.SessionStartloggt rein informativ Worktree + Git-Status (blockt nie).Stoperzwingt bei der Implementierungsstufe, dass.board/result.jsonexistiert, bevor die Session endet - sonst Exit 2, der Agent bekommt die Meldung zurück und kann (innerhalb seines--max-turns-Budgets) noch reagieren, statt kommentarlos ohne Ergebnis zu enden. Bewusst nicht bei Korrekturrunden eingehängt - deren Prompt sagt explizit "kein result.json nötig".
4. Guardrails - mehrere unabhängige Sicherheitsnetze
Guardrails sind alle Mechanismen, die verhindern, dass ein KI-Lauf etwas Falsches, Unerwünschtes oder schlicht Kaputtes auf Dauer anrichtet - typischerweise mehrere Schichten übereinander, damit nicht eine einzelne Lücke reicht. In diesem Projekt sind das konkret:
-
Generische Bash-Deny-Liste, doppelt abgesichert: der Hook aus
Abschnitt 3 blockt nicht mehr nur
git push, sondern eine ganze Musterliste gefährlicher Befehlsklassen (guard_hooks.find_denied_command()) - SSH/SCP auf andere Rechner,rmmit rekursiv+force, direktedocker stop/rm/kill/rmiauf benannte Container (docker composebleibt erlaubt), ein heruntergeladenes Skript percurl/wgetdirekt in eine Shell gepiped, undsudo. Zusätzlich doppelt abgesichert über--disallowedTools "Bash(git push:*)", "Bash(sudo:*)", "Bash(ssh:*)", "Bash(scp:*)"als zweite, unabhängige Sperre direkt im Tool-Filter von Claude Code (guards.DENY_DISALLOWED_TOOLS) - fällt eine Schicht aus, greift die andere noch.rm/docker/die Curl-Pipe hängen von Flag-Kombinationen ab, die--disallowedToolsnicht ausdrücken kann - dafür sorgt allein der Hook. -
Secrets-Schutz per Hook: derselbe Hook prüft seit 2026-10-23
zusätzlich jeden Dateinamen (
guard_hooks.is_secret_path()) - egal ob perRead/Edit/Write/MultiEdit/NotebookEditdirekt angefragt oder perBash(cat,cp,git add, ...) versucht. Blockt.env(außer bewusst committeten Vorlagen wie.env.example), SSH-Private-Keys (id_rsa,id_ed25519, ... -*.pubbleibt ausgenommen), Zertifikate/Keys (*.pem/*.key/*.p12/*.pfx) und Cloud-Credentials-JSONs. Der Matcher inguards.build_settings_file()musste dafür von nur"Bash"auf"Bash|Read|Write|Edit|MultiEdit|NotebookEdit"erweitert werden - sonst hätte ein direktesReadeiner.env-Datei den Hook nie ausgelöst. -
Tool-Zugriff pro Pipeline-Stufe eingeschränkt: die
PO-Verfeinerung läuft in einem leeren Temp-Ordner ganz ohne Schreibzugriff
(
_REFINE_DISALLOWED_TOOLS) - sie soll nur Text zu Text verarbeiten, nicht im Repo herumschauen. Der Testing-Agent läuft seit 2026-10-23 mit einer Allow-Liste statt einer Deny-Liste:--permission-mode default+--allowedTools Read,Grep,Glob(_TESTING_ALLOWED_TOOLS,run_testing_stage()) - "nichts erlaubt außer Lesen" statt "alles erlaubt außer Write/Edit/.../Bash". Deny-by-Default ist hier sauberer: ein neu hinzukommendes Tool (z.B. ein künftiges MCP-Tool) ist automatisch nicht verfügbar, statt es nachträglich in einer Deny-Liste ergänzen zu müssen. Grund laut Code-Kommentar fürs Nur-Lesen an sich: "sonst benotet sich der Schüler selbst". -
Unabhängiger Testing-Agent vor Merge/Deploy: ein zweiter,
separater
claude -p-Lauf prüft den fertigen Diff gegen die Akzeptanzkriterien und die allgemeine QA-Checkliste aus den Board-Settings (run_testing_stage()). Bei Ablehnung gehen die Befunde per--resumezurück an denselben Dev-Agenten (run_fix_round()), maximal zwei Korrekturrunden. Timeout oder unlesbare Antwort zählt als nicht bestanden (fail-closed), nie als automatisches Okay. -
Echtes Test-Gate vor Merge/Deploy:
guards.run_tests()führt dentest_cmdausapps.yamlselbst als Prozess aus und zählt nur dessen Exit-Code - nie die Selbstauskunft des Agenten. Nur ohne konfiguriertentest_cmdist das Gate fail-open (Hinweis: ohnetest_cmdpro App ist diese Schicht wirkungslos). -
Usage-Gate: vor jedem Ticket-Start prüft der Worker den aktuellen
5-Stunden-/7-Tage-Verbrauch (
worker/usage_gate.py) gegen Schwellen aus den Board-Settings und startet kein neues Ticket, wenn das Sebastians private Reserve angreifen würde - läuft eines bereits, kann es bei Überschreiten automatisch pausiert werden. -
Atomare Claims statt Vertrauen auf "wird schon nicht gleichzeitig passieren":
sowohl der echte Ticket-Claim als auch der PO-Verfeinerungs-Claim laufen über ein
SQL UPDATE ... WHERE status = ...am Server, das bei einer Race409zurückgibt statt zwei Worker-Läufe leise dasselbe Ticket bearbeiten zu lassen. -
Server erzwingt den Akzeptanzkriterien-Zwang, nicht nur die Oberfläche:
PATCH /tickets/:idlehnt den Wechsel nach "Zu implementieren" mit409ab, wennacceptance_criterialeer ist - das Drag&Drop im Board blockt zusätzlich fürs bessere Feedback, ist aber nicht die eigentliche Absicherung. -
Hängende-Tickets-Erkennung: rein zeitbasiert am Server
(
isStaleInProgress()) - markiert ein Ticket, das z.B. nach einem Worker-Absturz lautlos auf "In Arbeit" stehen geblieben ist, damit es nicht unbemerkt für immer dort hängt. -
Diff-Größen-Deckel: Worker kürzt den Diff vor dem Melden auf
400.000 Zeichen, der Server zusätzlich auf 500.000 - verhindert, dass ein
versehentlich mitgetrackter Build-Cache den Report-Call mit
413 Payload Too Largeabschießen und das Ticket lautlos auf "In Arbeit" stehen lassen kann (so passiert bei Ticket #22). -
Guardrail-Sichtbarkeit: ein Hook-Block verschwand bisher nur in
stderrdes jeweiligen Laufs - der Worker bekam nie mit, ob oder wie oft eine Sperre überhaupt greift.guard_hooks.pyschreibt jeden Block jetzt zusätzlich als JSON-Zeile in eine Log-Datei (ein Pfad pro Lauf, wichtig bei mehreren parallelen Implementierungs-Slots),guards.read_and_clear_hits()liest sie nach dem Lauf aus, undreport_run()meldet jeden Treffer perPOST /tickets/:id/guard-hitsals eigenen Timeline-Eintrag ("⛔ Guardrail blockierte: ...") ans Ticket.
Roadmap: Stand & Ausblick
Erst der komplette Überblick in einer Liste - was schon läuft, danach was als Nächstes kommt. Die ausführliche Erklärung zu jedem bereits umgesetzten Punkt steht in den vier Abschnitten oben (Links springen direkt dorthin).
Bereits umgesetzt
- Mehrstufige Pipeline Harness - Vorab-Check → Verfeinerung → Implementierung → Testing → bis zu 2 Korrekturrunden fertig
- Audit-Trail pro Lauf Harness - PostToolUse-Hook protokolliert jeden Tool-Call, aufklappbar in der Verbrauch-Tabelle fertig
- Plan-Review-Stufe vor der Implementierung Harness - read-only Plan + Risikoeinschätzung, Freigabe über den bestehenden Klärungs-Mechanismus bei echten Risikomerkmalen fertig
- Drei Settings-Ebenen Settings - Pro-Lauf-Datei, Board-weite Settings (live), globale statusLine fürs Usage-Gate fertig
- Generische Bash-Deny-Liste Hooks -
PreToolUseblockt git push, SSH/SCP, rm -rf, docker stop/rm/kill, curl|sh, sudo technisch, auch unterbypassPermissionsfertig - Secrets-Schutz per Hook Hooks - blockt Lesen/Schreiben/Committen von .env, SSH-Keys, Zertifikaten, Cloud-Credentials - per Read/Edit/Write UND per Bash fertig
- Lifecycle-Hooks (SessionStart/Stop) Hooks - SessionStart loggt den Start, Stop erzwingt .board/result.json vor Sessionende fertig
- Tool-Einschränkung pro Stufe Guardrails - Verfeinerung ganz ohne Repo-Zugriff, Testing-Agent per Allow-Liste nur lesend fertig
- Unabhängiger Testing-Agent Guardrails - bewertet den Diff gegen Akzeptanzkriterien + QA-Checkliste, fail-closed bei Timeout fertig
- Echtes Test-Gate Guardrails -
test_cmdausapps.yamlselbst ausgeführt, nur dessen Exit-Code zählt fertig - Usage-Gate Guardrails - Ticket-Start/Pause anhand 5h-/7-Tage-Verbrauch fertig
- Atomare Claims Guardrails - Ticket-Claim und Verfeinerungs-Claim race-sicher per SQL-
UPDATE ... WHEREfertig - Server-seitiger AC-Zwang Guardrails - "Zu implementieren" ohne Akzeptanzkriterien per
409blockiert, nicht nur die UI fertig - Hängende-Tickets-Erkennung Guardrails - zeitbasiert, unabhängig vom (ggf. abgestürzten) Worker fertig
- Diff-Größen-Deckel Guardrails - client- und serverseitig, verhindert
413-Abstürze fertig - Guardrail-Sichtbarkeit Guardrails - jeder Hook-Block landet als Timeline-Eintrag am Ticket, nicht nur in einem lokalen Log fertig
Geplant
Aktuell keine offenen Punkte - alle 15 Punkte dieser ersten Runde sind umgesetzt
(Stand 2026-10-23). Nach demselben Muster wie die "Orchestrierungs-Roadmap"
(Tickets #10-#14, siehe ticket-board/CLAUDE.md): neue Punkte werden als
eigenes Ticket im Board angelegt und vom Worker selbst umgesetzt (Dogfooding).