Vai al contenuto principale
Questo documento spiega come failproofai funziona internamente: come il sistema hook intercetta le chiamate agli strumenti dell’agente, come viene caricata e unita la configurazione, come vengono valutate le policy e come il dashboard monitora l’attività dell’agente.

Panoramica

failproofai ha due sottosistemi indipendenti:
  1. Gestore hook - Un veloce sottoprocesso CLI che Claude Code invoca su ogni chiamata dello strumento dell’agente. Valuta le policy e restituisce una decisione.
  2. Agent Monitor (Dashboard) - Un’applicazione web Next.js per monitorare le sessioni dell’agente e gestire le policy.
Entrambi i sottosistemi condividono file di configurazione in ~/.failproofai/ e nella directory .failproofai/ del progetto, ma vengono eseguiti come processi separati e comunicano solo attraverso il filesystem.

Gestore hook

Integrazione con Claude Code

Quando esegui failproofai policies --install, scrive voci come questa in ~/.claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
Claude Code quindi invoca failproofai --hook PreToolUse come sottoprocesso prima di ogni chiamata dello strumento, passando un payload JSON su stdin.

Formato del payload

{
  "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" }
}
Per gli eventi PostToolUse, il payload contiene anche tool_result con l’output dello strumento. Il gestore impone un limite di stdin di 1 MB. I payload che superano questo limite vengono scartati e tutte le policy consentono implicitamente.

Formato della risposta

Nega (PreToolUse):
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
Nega (PostToolUse):
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
Istruisci (qualsiasi evento eccetto Stop):
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
Istruisci evento Stop:
  • Codice di uscita: 2
  • Motivo scritto su stderr (non stdout)
Consenti:
  • Codice di uscita: 0
  • Stdout vuoto
Consenti con messaggio: allow(message) consente a una policy di inviare un contesto informativo a Claude anche quando l’operazione è consentita. Il gestore hook scrive il seguente JSON su stdout (non su un file di configurazione — questa è la risposta del gestore a Claude Code, proprio come le risposte deny e instruct sopra):
// Scritto su stdout dal processo del gestore hook
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
  • Codice di uscita: 0 (operazione consentita)
  • Quando più policy restituiscono allow con un messaggio, i loro messaggi vengono uniti con newline in una singola stringa additionalContext
  • Se nessuna policy fornisce un messaggio, stdout è vuoto (stesso di prima)

Pipeline di elaborazione

src/hooks/handler.ts implementa la pipeline completa:
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
L’intero processo viene eseguito in meno di 100ms per payload tipici senza chiamate LLM.

Caricamento della configurazione

src/hooks/hooks-config.ts implementa il caricamento della configurazione a tre livelli.
[1] {cwd}/.failproofai/policies-config.json        ← project  (highest priority)
[2] {cwd}/.failproofai/policies-config.local.json  ← local
[3] ~/.failproofai/policies-config.json             ← global   (lowest priority)
Logica di unione:
  • enabledPolicies - unione deduplicate tra tutti e tre i file
  • policyParams - per policy, il primo file che lo definisce vince completamente
  • customPoliciesPath - il primo file che lo definisce vince
  • llm - il primo file che lo definisce vince
Il dashboard web utilizza readHooksConfig() (solo globale) per leggere e scrivere, poiché non viene invocato con un cwd di progetto.

Valutazione delle policy

src/hooks/policy-evaluator.ts esegue le policy in ordine. Per ogni policy:
  1. Cercare lo schema params della policy (se ne ha uno).
  2. Leggere policyParams[policy.name] dalla configurazione unita.
  3. Unire i valori forniti dall’utente sui default dello schema per produrre ctx.params.
  4. Chiamare policy.fn(ctx) con il contesto risolto.
  5. Se il risultato è deny, fermarsi immediatamente e restituire quella decisione.
  6. Se il risultato è instruct, accumulare il messaggio e continuare.
  7. Se il risultato è allow, continuare alla policy successiva.
Dopo che tutte le policy vengono eseguite:
  • Se è stato restituito un deny, emettere la risposta deny.
  • Se sono stati raccolti ritorni instruct, emettere una singola risposta instruct con tutti i messaggi uniti.
  • Altrimenti, emettere una risposta allow (stdout vuoto, uscita 0).

Policy integrate

src/hooks/builtin-policies.ts definisce tutte le 39 policy integrate come oggetti BuiltinPolicyDefinition:
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
Le policy che accettano params dichiarano un PolicyParamsSchema con tipi e default per ogni parametro. Il valutatore di policy inietta i valori risolti in ctx.params prima di chiamare fn. Le funzioni di policy leggono ctx.params senza protezione null perché i default vengono sempre applicati per primo. La corrispondenza di pattern all’interno delle policy utilizza token di comando analizzati (argv), non corrispondenza di stringhe grezze. Questo previene il bypass tramite iniezione di operatori shell (ad es. un pattern per sudo systemctl status * non può essere bypassato aggiungendo ; rm -rf / al comando).

Policy personalizzate

src/hooks/custom-hooks-registry.ts implementa un registro supportato da globalThis:
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 carica il file di policy dell’utente:
  1. Leggere customPoliciesPath dalla configurazione; saltare se assente.
  2. Risolvere al percorso assoluto; controllare che il file esista.
  3. Riscrivere tutti gli import from "failproofai" al percorso dist effettivo in modo che customPolicies si risolva nello stesso registro globalThis.
  4. Riscrivere ricorsivamente gli import locali transitivi per garantire la compatibilità ESM.
  5. Scrivere file .mjs temporanei e import() il file di ingresso.
  6. Chiamare getCustomHooks() per recuperare gli hook registrati.
  7. Pulire tutti i file temporanei in un blocco finally.
Su qualsiasi errore (file non trovato, errore di sintassi, errore di importazione), l’errore viene registrato in ~/.failproofai/hook.log e il loader restituisce un array vuoto. Le policy integrate non sono interessate. Le policy personalizzate vengono valutate dopo tutte le policy integrate. Un deny di una policy personalizzata ancora cortocircuita ulteriori policy personalizzate (ma tutte le politiche integrate sono già state eseguite a quel punto).

Registrazione dell’attività

Dopo ogni evento hook, il gestore aggiunge una riga JSONL a ~/.failproofai/hook-activity.jsonl:
{
  "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
}
Una riga per ogni policy che ha preso una decisione non-allow. Le decisioni allow non vengono registrate (per mantenere il file piccolo).

Architettura del dashboard

Il dashboard è un’applicazione Next.js 16 che utilizza l’App Router con React Server Components e Server Actions.
app/
  layout.tsx                  ← Root layout (theme, telemetry, nav)
  projects/page.tsx           ← Server component: list all Claude projects
  project/[name]/page.tsx     ← Server component: list sessions in a project
  project/[name]/session/
    [sessionId]/page.tsx      ← Server component: render session viewer
  policies/page.tsx           ← Client component: policy management + activity log
  actions/
    get-hooks-config.ts       ← Read config + policy list
    update-hooks-config.ts    ← Toggle policy on/off
    update-policy-params.ts   ← Update policy parameters
    get-hook-activity.ts      ← Paginate/search activity log
    install-hooks-web.ts      ← Install/remove hooks from the browser
  api/
    download/[project]/[session]/route.ts   ← Per-CLI session export (JSONL or JSON)
Flusso dei dati:
  • I componenti della pagina chiamano lib/projects.ts e lib/log-entries.ts per leggere i dati del progetto/sessione direttamente dal filesystem (nessun livello API per le letture).
  • La pagina Policies utilizza Server Actions per tutte le mutazioni (toggle, aggiornamento parametri, installazione/rimozione).
  • Il visualizzatore della sessione analizza il formato di trascritto JSONL di Claude e visualizza una timeline di messaggi e chiamate ai strumenti.
Decisioni di progettazione chiave:
  • Nessun database - tutto lo stato persistente si trova in file semplici (~/.failproofai/, ~/.claude/projects/).
  • Server Actions per le mutazioni - nessuna API REST necessaria per le operazioni CRUD.
  • React Server Components per le pagine di lettura - caricamento iniziale più veloce, nessun bundle client per il recupero dei dati.
  • Componenti client solo dove è necessaria l’interattività (toggle policy, ricerca attività, visualizzatore log).

Layout dei file

failproofai/
├── bin/
│   └── failproofai.mjs           # CLI router (hook / dashboard / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Hook event pipeline
│   ├── builtin-policies.ts       # 39 policy definitions
│   ├── policy-evaluator.ts       # Policy execution engine
│   ├── policy-registry.ts        # Policy registration and lookup
│   ├── policy-types.ts           # TypeScript interfaces
│   ├── hooks-config.ts           # Multi-scope config loading
│   ├── custom-hooks-registry.ts  # globalThis-backed hook registry
│   ├── custom-hooks-loader.ts    # ESM loader for user JS hooks
│   ├── manager.ts                # install / remove / list operations
│   ├── install-prompt.ts         # Interactive policy selection prompt
│   ├── hook-logger.ts            # Logging to hook.log
│   ├── hook-activity-store.ts    # Persist activity to hook-activity.jsonl
│   └── llm-client.ts             # LLM API client (for AI-powered policies)
├── app/                          # Next.js dashboard (pages + server actions)
├── lib/                          # Shared utilities
│   ├── projects.ts               # Enumerate Claude projects from filesystem
│   ├── log-entries.ts            # Parse Claude transcript JSONL format
│   ├── paths.ts                  # Resolve system paths
│   └── ...
├── components/                   # Shared React UI components
├── contexts/                     # React context providers (theme, auto-refresh, telemetry)
├── examples/                     # Example custom hook files
└── __tests__/                    # Unit and E2E tests