Pular para o conteúdo principal
Este documento explica como o failproofai funciona internamente: como o sistema de hooks intercepta chamadas de ferramentas do agente, como a configuração é carregada e mesclada, como as políticas são avaliadas e como o dashboard monitora a atividade do agente.

Visão geral

O failproofai possui dois subsistemas independentes:
  1. Hook handler - Um subprocesso CLI rápido que o Claude Code invoca a cada chamada de ferramenta do agente. Avalia as políticas e retorna uma decisão.
  2. Monitor de Agentes (Dashboard) - Uma aplicação web Next.js para monitorar sessões de agentes e gerenciar políticas.
Ambos os subsistemas compartilham arquivos de configuração em ~/.failproofai/ e no diretório .failproofai/ do projeto, mas são executados como processos separados e se comunicam apenas pelo sistema de arquivos.

Hook handler

Integração com o Claude Code

Quando você executa failproofai policies --install, ele escreve entradas como esta em ~/.claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
O Claude Code então invoca failproofai --hook PreToolUse como subprocesso antes de cada chamada de ferramenta, passando um payload JSON via stdin.

Formato do 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" }
}
Para eventos PostToolUse, o payload também contém tool_result com a saída da ferramenta. O handler impõe um limite de 1 MB para stdin. Payloads que excedam esse tamanho são descartados e todas as políticas permitem implicitamente.

Formato da resposta

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 (qualquer evento exceto Stop):
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
Instruct para evento Stop:
  • Código de saída: 2
  • Motivo escrito em stderr (não em stdout)
Allow:
  • Código de saída: 0
  • stdout vazio
Allow com mensagem: allow(message) permite que uma política envie contexto informativo de volta ao Claude mesmo quando a operação é permitida. O hook handler escreve o seguinte JSON em stdout (não em um arquivo de configuração — esta é a resposta do handler ao Claude Code, assim como as respostas de deny e instruct acima):
// Written to stdout by the hook handler process
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
  • Código de saída: 0 (a operação é permitida)
  • Quando múltiplas políticas retornam allow com uma mensagem, suas mensagens são unidas com quebras de linha em uma única string additionalContext
  • Se nenhuma política fornecer uma mensagem, stdout fica vazio (igual ao comportamento anterior)

Pipeline de processamento

src/hooks/handler.ts implementa o pipeline completo:
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
Todo o processo é executado em menos de 100ms para payloads típicos, sem nenhuma chamada a LLMs.

Carregamento de configuração

src/hooks/hooks-config.ts implementa o carregamento de configuração em três escopos.
[1] {cwd}/.failproofai/policies-config.json        ← projeto   (maior prioridade)
[2] {cwd}/.failproofai/policies-config.local.json  ← local
[3] ~/.failproofai/policies-config.json             ← global    (menor prioridade)
Lógica de mesclagem:
  • enabledPolicies - união sem duplicatas entre os três arquivos
  • policyParams - por chave de política, o primeiro arquivo que a define vence inteiramente
  • customPoliciesPath - o primeiro arquivo que a define vence
  • llm - o primeiro arquivo que a define vence
O dashboard web utiliza readHooksConfig() (somente global) para leitura e escrita, pois não é invocado com um cwd de projeto.

Avaliação de políticas

src/hooks/policy-evaluator.ts executa as políticas em ordem. Para cada política:
  1. Busca o esquema de params da política (se houver).
  2. policyParams[policy.name] da configuração mesclada.
  3. Mescla os valores fornecidos pelo usuário sobre os padrões do esquema para produzir ctx.params.
  4. Chama policy.fn(ctx) com o contexto resolvido.
  5. Se o resultado for deny, interrompe imediatamente e retorna essa decisão.
  6. Se o resultado for instruct, acumula a mensagem e continua.
  7. Se o resultado for allow, continua para a próxima política.
Após todas as políticas serem executadas:
  • Se algum deny foi retornado, emite a resposta de deny.
  • Se algum retorno instruct foi coletado, emite uma única resposta instruct com todas as mensagens unidas.
  • Caso contrário, emite uma resposta allow (stdout vazio, saída 0).

Políticas embutidas

src/hooks/builtin-policies.ts define todas as 26 políticas embutidas como objetos BuiltinPolicyDefinition:
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
Políticas que aceitam params declaram um PolicyParamsSchema com tipos e valores padrão para cada parâmetro. O avaliador de políticas injeta os valores resolvidos em ctx.params antes de chamar fn. As funções de política leem ctx.params sem verificações de nulo porque os padrões são sempre aplicados primeiro. A correspondência de padrões dentro das políticas usa tokens de comando analisados (argv), não correspondência de strings brutas. Isso evita bypass por injeção de operadores shell (por exemplo, um padrão para sudo systemctl status * não pode ser ignorado anexando ; rm -rf / ao comando).

Políticas personalizadas

src/hooks/custom-hooks-registry.ts implementa um registro baseado em 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 carrega o arquivo de políticas do usuário:
  1. customPoliciesPath da configuração; ignora se ausente.
  2. Resolve para caminho absoluto; verifica se o arquivo existe.
  3. Reescreve todas as importações from "failproofai" para o caminho real do dist, de modo que customPolicies resolva para o mesmo registro globalThis.
  4. Reescreve recursivamente importações locais transitivas para garantir compatibilidade ESM.
  5. Grava arquivos .mjs temporários e realiza import() do arquivo de entrada.
  6. Chama getCustomHooks() para recuperar os hooks registrados.
  7. Limpa todos os arquivos temporários em um bloco finally.
Em caso de qualquer erro (arquivo não encontrado, erro de sintaxe, falha na importação), o erro é registrado em ~/.failproofai/hook.log e o loader retorna um array vazio. As políticas embutidas não são afetadas. As políticas personalizadas são avaliadas após todas as políticas embutidas. Um deny de política personalizada ainda interrompe as demais políticas personalizadas (mas todos os embutidos já terão sido executados nesse ponto).

Log de atividade

Após cada evento de hook, o handler anexa uma linha JSONL em ~/.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
}
Uma linha por política que tomou uma decisão diferente de allow. Decisões allow não são registradas (para manter o arquivo pequeno).

Arquitetura do dashboard

O dashboard é uma aplicação Next.js 16 usando o App Router com React Server Components e Server Actions.
app/
  layout.tsx                  ← Layout raiz (tema, telemetria, navegação)
  projects/page.tsx           ← Server component: lista todos os projetos Claude
  project/[name]/page.tsx     ← Server component: lista sessões em um projeto
  project/[name]/session/
    [sessionId]/page.tsx      ← Server component: renderiza o visualizador de sessão
  policies/page.tsx           ← Client component: gerenciamento de políticas + log de atividade
  actions/
    get-hooks-config.ts       ← Lê configuração + lista de políticas
    update-hooks-config.ts    ← Ativa/desativa política
    update-policy-params.ts   ← Atualiza parâmetros da política
    get-hook-activity.ts      ← Paginação/busca no log de atividade
    install-hooks-web.ts      ← Instala/remove hooks pelo navegador
  api/
    download/[project]/[session]/route.ts   ← Exporta sessão como ZIP/JSONL
Fluxo de dados:
  • Os componentes de página chamam lib/projects.ts e lib/log-entries.ts para ler dados de projeto/sessão diretamente do sistema de arquivos (sem camada de API para leituras).
  • A página de Políticas usa Server Actions para todas as mutações (alternar, atualizar parâmetros, instalar/remover).
  • O visualizador de sessão analisa o formato de transcrição JSONL do Claude e renderiza uma linha do tempo de mensagens e chamadas de ferramentas.
Principais decisões de design:
  • Sem banco de dados - todo estado persistente está em arquivos simples (~/.failproofai/, ~/.claude/projects/).
  • Server Actions para mutações - sem necessidade de API REST para operações CRUD.
  • React Server Components para páginas de leitura - carregamento inicial mais rápido, sem bundle no cliente para busca de dados.
  • Client components apenas onde interatividade é necessária (alternância de políticas, busca de atividade, visualizador de log).

Estrutura de arquivos

failproofai/
├── bin/
│   └── failproofai.mjs           # Roteador CLI (hook / dashboard / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Pipeline de eventos de hook
│   ├── builtin-policies.ts       # 26 definições de políticas
│   ├── policy-evaluator.ts       # Motor de execução de políticas
│   ├── policy-registry.ts        # Registro e busca de políticas
│   ├── policy-types.ts           # Interfaces TypeScript
│   ├── hooks-config.ts           # Carregamento de configuração multi-escopo
│   ├── custom-hooks-registry.ts  # Registro de hooks baseado em globalThis
│   ├── custom-hooks-loader.ts    # Carregador ESM para hooks JS do usuário
│   ├── manager.ts                # Operações de instalação / remoção / listagem
│   ├── install-prompt.ts         # Prompt interativo de seleção de políticas
│   ├── hook-logger.ts            # Registro em hook.log
│   ├── hook-activity-store.ts    # Persiste atividade em hook-activity.jsonl
│   └── llm-client.ts             # Cliente de API LLM (para políticas com IA)
├── app/                          # Dashboard Next.js (páginas + server actions)
├── lib/                          # Utilitários compartilhados
│   ├── projects.ts               # Enumera projetos Claude do sistema de arquivos
│   ├── log-entries.ts            # Analisa o formato JSONL de transcrição do Claude
│   ├── paths.ts                  # Resolve caminhos do sistema
│   └── ...
├── components/                   # Componentes React de UI compartilhados
├── contexts/                     # Provedores de contexto React (tema, atualização automática, telemetria)
├── examples/                     # Exemplos de arquivos de hooks personalizados
└── __tests__/                    # Testes unitários e E2E