Zum Hauptinhalt springen
Dieses Dokument erklärt, wie failproofai intern funktioniert: wie das Hook-System Agent-Tool-Aufrufe abfängt, wie die Konfiguration geladen und zusammengeführt wird, wie Policies ausgewertet werden und wie das Dashboard die Agentenaktivität überwacht.

Überblick

failproofai besteht aus zwei unabhängigen Subsystemen:
  1. Hook-Handler – Ein schneller CLI-Subprocess, den Claude Code bei jedem Agent-Tool-Aufruf aufruft. Wertet Policies aus und gibt eine Entscheidung zurück.
  2. Agent Monitor (Dashboard) – Eine Next.js-Webanwendung zur Überwachung von Agentensitzungen und zur Verwaltung von Policies.
Beide Subsysteme teilen sich Konfigurationsdateien in ~/.failproofai/ und im .failproofai/-Verzeichnis des Projekts, laufen jedoch als separate Prozesse und kommunizieren ausschließlich über das Dateisystem.

Hook-Handler

Integration mit Claude Code

Wenn Sie failproofai policies --install ausführen, schreibt es folgende Einträge in ~/.claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
Claude Code ruft daraufhin failproofai --hook PreToolUse als Subprocess vor jedem Tool-Aufruf auf und übergibt dabei einen JSON-Payload über stdin.

Payload-Format

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
Bei PostToolUse-Events enthält der Payload zusätzlich tool_result mit der Ausgabe des Tools. Der Handler setzt ein Limit von 1 MB für stdin. Payloads, die dieses Limit überschreiten, werden verworfen und alle Policies erlauben implizit.

Antwortformat

Deny (PreToolUse):
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
Deny (PostToolUse):
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
Instruct (alle Events außer Stop):
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
Stop-Event instruct:
  • Exit-Code: 2
  • Grund wird nach stderr geschrieben (nicht stdout)
Allow:
  • Exit-Code: 0
  • Leerer stdout
Allow mit Nachricht: allow(message) ermöglicht einer Policy, informativen Kontext an Claude zurückzusenden, auch wenn die Operation erlaubt wird. Der Hook-Handler schreibt folgendes JSON nach stdout (keine Konfigurationsdatei – dies ist die Antwort des Handlers an Claude Code, genau wie deny- und instruct-Antworten oben):
// Written to stdout by the hook handler process
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
  • Exit-Code: 0 (Operation ist erlaubt)
  • Wenn mehrere Policies allow mit einer Nachricht zurückgeben, werden ihre Nachrichten mit Zeilenumbrüchen zu einem einzigen additionalContext-String verbunden
  • Wenn keine Policy eine Nachricht liefert, ist stdout leer (wie zuvor)

Verarbeitungspipeline

src/hooks/handler.ts implementiert die vollständige Pipeline:
stdin JSON
  → parse payload (max 1 MB)
  → extract session metadata (session_id, cwd, tool_name, tool_input, etc.)
  → readMergedHooksConfig(cwd)    ← merges project + local + global config
  → register enabled builtin policies with resolved params
  → load custom policies from customPoliciesPath (if set)
  → register custom policies into policy registry
  → evaluate all policies (builtins first, then custom)
      → first deny short-circuits
      → instruct decisions accumulate
      → allow messages accumulate
  → write JSON decision to stdout
  → persist event to ~/.failproofai/hook-activity.jsonl
  → exit
Der gesamte Prozess läuft bei typischen Payloads ohne LLM-Aufrufe in unter 100 ms ab.

Konfiguration laden

src/hooks/hooks-config.ts implementiert das dreistufige Laden der Konfiguration.
[1] {cwd}/.failproofai/policies-config.json        ← Projekt  (höchste Priorität)
[2] {cwd}/.failproofai/policies-config.local.json  ← lokal
[3] ~/.failproofai/policies-config.json             ← global   (niedrigste Priorität)
Zusammenführungslogik:
  • enabledPolicies – deduplizierte Vereinigung aller drei Dateien
  • policyParams – pro Policy-Schlüssel gewinnt die erste Datei, die ihn definiert, vollständig
  • customPoliciesPath – die erste Datei, die ihn definiert, gewinnt
  • llm – die erste Datei, die ihn definiert, gewinnt
Das Web-Dashboard verwendet readHooksConfig() (nur global) zum Lesen und Schreiben, da es nicht mit einem Projekt-cwd aufgerufen wird.

Policy-Auswertung

src/hooks/policy-evaluator.ts führt Policies der Reihe nach aus. Für jede Policy:
  1. Das params-Schema der Policy nachschlagen (sofern vorhanden).
  2. policyParams[policy.name] aus der zusammengeführten Konfiguration lesen.
  3. Benutzerdefinierte Werte über Schema-Standardwerte zusammenführen, um ctx.params zu erzeugen.
  4. policy.fn(ctx) mit dem aufgelösten Kontext aufrufen.
  5. Ist das Ergebnis deny, sofort stoppen und diese Entscheidung zurückgeben.
  6. Ist das Ergebnis instruct, die Nachricht sammeln und fortfahren.
  7. Ist das Ergebnis allow, mit der nächsten Policy fortfahren.
Nach Ausführung aller Policies:
  • Wenn ein deny zurückgegeben wurde, die Deny-Antwort ausgeben.
  • Wenn instruct-Rückgaben gesammelt wurden, eine einzelne instruct-Antwort mit allen verbundenen Nachrichten ausgeben.
  • Andernfalls eine Allow-Antwort ausgeben (leerer stdout, Exit 0).

Eingebaute Policies

src/hooks/builtin-policies.ts definiert alle 26 eingebauten Policies als BuiltinPolicyDefinition-Objekte:
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
Policies, die params akzeptieren, deklarieren ein PolicyParamsSchema mit Typen und Standardwerten für jeden Parameter. Der Policy-Evaluator injiziert aufgelöste Werte in ctx.params, bevor fn aufgerufen wird. Policy-Funktionen lesen ctx.params ohne Null-Prüfung, da Standardwerte immer zuerst angewendet werden. Pattern-Matching innerhalb von Policies verwendet geparste Befehlstoken (argv) statt einfachen String-Vergleichen. Dies verhindert Umgehungsversuche durch Shell-Operator-Injection (z. B. kann ein Muster für sudo systemctl status * nicht durch Anhängen von ; rm -rf / an den Befehl umgangen werden).

Benutzerdefinierte Policies

src/hooks/custom-hooks-registry.ts implementiert eine globalThis-basierte Registry:
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // used in tests
src/hooks/custom-hooks-loader.ts lädt die Policy-Datei des Benutzers:
  1. customPoliciesPath aus der Konfiguration lesen; überspringen, falls nicht vorhanden.
  2. Zu absolutem Pfad auflösen; prüfen, ob die Datei existiert.
  3. Alle from "failproofai"-Imports zum tatsächlichen dist-Pfad umschreiben, damit customPolicies zur selben globalThis-Registry aufgelöst wird.
  4. Transitive lokale Imports rekursiv umschreiben, um ESM-Kompatibilität sicherzustellen.
  5. Temporäre .mjs-Dateien schreiben und die Einstiegsdatei per import() laden.
  6. getCustomHooks() aufrufen, um registrierte Hooks abzurufen.
  7. Alle temporären Dateien in einem finally-Block bereinigen.
Bei jedem Fehler (Datei nicht gefunden, Syntaxfehler, Import-Fehler) wird der Fehler in ~/.failproofai/hook.log protokolliert und der Loader gibt ein leeres Array zurück. Eingebaute Policies sind davon nicht betroffen. Benutzerdefinierte Policies werden nach allen eingebauten Policies ausgewertet. Ein deny einer benutzerdefinierten Policy unterbricht dennoch weitere benutzerdefinierte Policies (alle eingebauten Policies sind zu diesem Zeitpunkt jedoch bereits ausgeführt worden).

Aktivitätsprotokollierung

Nach jedem Hook-Event hängt der Handler eine JSONL-Zeile an ~/.failproofai/hook-activity.jsonl an:
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
Eine Zeile pro Policy, die eine Nicht-Allow-Entscheidung getroffen hat. Allow-Entscheidungen werden nicht protokolliert (um die Dateigröße gering zu halten).

Dashboard-Architektur

Das Dashboard ist eine Next.js 16-Anwendung, die den App Router mit React Server Components und Server Actions verwendet.
app/
  layout.tsx                  ← Root-Layout (Theme, Telemetrie, Navigation)
  projects/page.tsx           ← Server-Komponente: alle Claude-Projekte auflisten
  project/[name]/page.tsx     ← Server-Komponente: Sitzungen in einem Projekt auflisten
  project/[name]/session/
    [sessionId]/page.tsx      ← Server-Komponente: Sitzungsansicht rendern
  policies/page.tsx           ← Client-Komponente: Policy-Verwaltung + Aktivitätslog
  actions/
    get-hooks-config.ts       ← Konfiguration + Policy-Liste lesen
    update-hooks-config.ts    ← Policy ein-/ausschalten
    update-policy-params.ts   ← Policy-Parameter aktualisieren
    get-hook-activity.ts      ← Aktivitätslog paginieren/durchsuchen
    install-hooks-web.ts      ← Hooks über den Browser installieren/entfernen
  api/
    download/[project]/[session]/route.ts   ← Sitzung als ZIP/JSONL exportieren
Datenfluss:
  • Seitenkomponenten rufen lib/projects.ts und lib/log-entries.ts auf, um Projekt-/Sitzungsdaten direkt aus dem Dateisystem zu lesen (keine API-Schicht für Lesevorgänge).
  • Die Policies-Seite verwendet Server Actions für alle Mutationen (Umschalten, Parameter-Aktualisierung, Installieren/Entfernen).
  • Der Sitzungs-Viewer parst das JSONL-Transkriptformat von Claude und rendert einen Zeitstrahl von Nachrichten und Tool-Aufrufen.
Wesentliche Designentscheidungen:
  • Keine Datenbank – der gesamte persistente Zustand liegt in einfachen Dateien (~/.failproofai/, ~/.claude/projects/).
  • Server Actions für Mutationen – kein REST-API für CRUD-Operationen erforderlich.
  • React Server Components für Lese-Seiten – schnelleres initiales Laden, kein Client-Bundle für Datenabruf.
  • Client-Komponenten nur dort, wo Interaktivität benötigt wird (Policy-Umschalter, Aktivitätssuche, Log-Viewer).

Dateistruktur

failproofai/
├── bin/
│   └── failproofai.mjs           # CLI-Router (hook / dashboard / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Hook-Event-Pipeline
│   ├── builtin-policies.ts       # 26 Policy-Definitionen
│   ├── policy-evaluator.ts       # Policy-Ausführungs-Engine
│   ├── policy-registry.ts        # Policy-Registrierung und -Nachschlagen
│   ├── policy-types.ts           # TypeScript-Interfaces
│   ├── hooks-config.ts           # Mehrstufiges Laden der Konfiguration
│   ├── custom-hooks-registry.ts  # globalThis-basierte Hook-Registry
│   ├── custom-hooks-loader.ts    # ESM-Loader für benutzerdefinierte JS-Hooks
│   ├── manager.ts                # Installations-/Entfernen-/Auflistungsoperationen
│   ├── install-prompt.ts         # Interaktive Policy-Auswahlabfrage
│   ├── hook-logger.ts            # Protokollierung nach hook.log
│   ├── hook-activity-store.ts    # Aktivität nach hook-activity.jsonl persistieren
│   └── llm-client.ts             # LLM-API-Client (für KI-gestützte Policies)
├── app/                          # Next.js-Dashboard (Seiten + Server Actions)
├── lib/                          # Gemeinsame Hilfsfunktionen
│   ├── projects.ts               # Claude-Projekte aus dem Dateisystem aufzählen
│   ├── log-entries.ts            # Claude-Transkript-JSONL-Format parsen
│   ├── paths.ts                  # Systempfade auflösen
│   └── ...
├── components/                   # Gemeinsame React-UI-Komponenten
├── contexts/                     # React-Context-Provider (Theme, Auto-Refresh, Telemetrie)
├── examples/                     # Beispieldateien für benutzerdefinierte Hooks
└── __tests__/                    # Unit- und E2E-Tests