As políticas personalizadas permitem que você escreva regras para qualquer comportamento do agente: aplicar convenções do projeto, prevenir desvios, controlar operações destrutivas, detectar agentes travados ou integrar com Slack, fluxos de aprovação e muito mais. Elas usam o mesmo sistema de eventos de hook e as decisões allow, deny, instruct das políticas embutidas.
Exemplo rápido
// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";
customPolicies.add({
name: "no-production-writes",
description: "Block writes to paths containing 'production'",
match: { events: ["PreToolUse"] },
fn: async (ctx) => {
if (ctx.toolName !== "Write" && ctx.toolName !== "Edit") return allow();
const path = ctx.toolInput?.file_path ?? "";
if (path.includes("production")) {
return deny("Writes to production paths are blocked");
}
return allow();
},
});
Instale:
failproofai policies --install --custom ./my-policies.js
Opção 1: Baseada em convenção (recomendada)
Coloque arquivos *policies.{js,mjs,ts} dentro de .failproofai/policies/ e eles serão carregados automaticamente — sem flags ou alterações de configuração. Funciona como git hooks: coloque o arquivo e pronto.
# Nível do projeto — commitado no git, compartilhado com o time
.failproofai/policies/security-policies.mjs
.failproofai/policies/workflow-policies.mjs
# Nível do usuário — pessoal, aplicado a todos os projetos
~/.failproofai/policies/my-policies.mjs
Como funciona:
- Tanto o diretório do projeto quanto o do usuário são escaneados (união — não prevalece o primeiro escopo encontrado)
- Os arquivos são carregados em ordem alfabética dentro de cada diretório. Use prefixos
01-, 02- para controlar a ordem
- Apenas arquivos que correspondem a
*policies.{js,mjs,ts} são carregados; outros arquivos são ignorados
- Cada arquivo é carregado de forma independente (fail-open por arquivo)
- Funciona junto com
--custom explícito e políticas embutidas
As políticas por convenção são a forma mais fácil de compartilhar políticas com um time. Faça commit de .failproofai/policies/ no git e todos os membros do time as recebem automaticamente.
Opção 2: Caminho de arquivo explícito
# Instalar com um arquivo de políticas personalizado
failproofai policies --install --custom ./my-policies.js
# Substituir o caminho do arquivo de políticas
failproofai policies --install --custom ./new-policies.js
# Remover o caminho de políticas personalizadas da configuração
failproofai policies --uninstall --custom
O caminho absoluto resolvido é armazenado em policies-config.json como customPoliciesPath. O arquivo é carregado novamente a cada evento de hook — não há cache entre eventos.
Usando as duas opções juntas
As políticas por convenção e o arquivo --custom explícito podem coexistir. Ordem de carregamento:
- Arquivo
customPoliciesPath explícito (se configurado)
- Arquivos de convenção do projeto (
{cwd}/.failproofai/policies/, ordem alfabética)
- Arquivos de convenção do usuário (
~/.failproofai/policies/, ordem alfabética)
API
Importação
import { customPolicies, allow, deny, instruct } from "failproofai";
customPolicies.add(hook)
Registra uma política. Chame quantas vezes forem necessárias para múltiplas políticas no mesmo arquivo.
customPolicies.add({
name: string; // obrigatório - identificador único
description?: string; // exibido na saída de `failproofai policies`
match?: { events?: HookEventType[] }; // filtra por tipo de evento; omita para corresponder a todos
fn: (ctx: PolicyContext) => PolicyResult | Promise<PolicyResult>;
});
Helpers de decisão
| Função | Efeito | Use quando |
|---|
allow() | Permite a operação silenciosamente | A ação é segura, nenhuma mensagem é necessária |
deny(message) | Bloqueia a operação | O agente não deve executar esta ação |
instruct(message) | Adiciona contexto sem bloquear | Fornece contexto adicional ao agente para mantê-lo no caminho certo |
deny(message) — a mensagem aparece para Claude com o prefixo "Blocked by failproofai:". Um único deny interrompe toda avaliação posterior.
instruct(message) — a mensagem é anexada ao contexto de Claude para a chamada de ferramenta atual. Todas as mensagens instruct são acumuladas e entregues juntas.
Você pode adicionar orientações extras a qualquer mensagem deny ou instruct incluindo um campo hint em policyParams — sem necessidade de alteração de código. Isso funciona para políticas personalizadas (custom/), de convenção do projeto (.failproofai-project/) e de convenção do usuário (.failproofai-user/) também. Veja Configuração → hint para detalhes.
allow(message) permite a operação e envia uma mensagem informativa de volta para Claude. A mensagem é entregue como additionalContext na resposta stdout do handler de hook — o mesmo mecanismo usado por instruct, mas semanticamente diferente: é uma atualização de status, não um aviso.
| Função | Efeito | Use quando |
|---|
allow(message) | Permite e envia contexto para Claude | Confirmar que uma verificação passou, ou explicar por que foi ignorada |
Casos de uso:
- Confirmações de status:
allow("All CI checks passed.") — informa Claude que tudo está ok
- Explicações de fail-open:
allow("GitHub CLI not installed, skipping CI check.") — informa Claude por que uma verificação foi ignorada, para que tenha contexto completo
- Múltiplas mensagens se acumulam: se várias políticas retornarem
allow(message), todas as mensagens são unidas com quebras de linha e entregues juntas
customPolicies.add({
name: "confirm-branch-status",
match: { events: ["Stop"] },
fn: async (ctx) => {
const cwd = ctx.session?.cwd;
if (!cwd) return allow("No working directory, skipping branch check.");
// ... check branch status ...
if (allPushed) {
return allow("Branch is up to date with remote.");
}
return deny("Unpushed changes detected.");
},
});
Campos de PolicyContext
| Campo | Tipo | Descrição |
|---|
eventType | string | "PreToolUse", "PostToolUse", "Notification", "Stop" |
toolName | string | undefined | A ferramenta sendo chamada (ex.: "Bash", "Write", "Read") |
toolInput | Record<string, unknown> | undefined | Os parâmetros de entrada da ferramenta |
payload | Record<string, unknown> | Payload bruto completo do evento do Claude Code |
session | SessionMetadata | undefined | Contexto da sessão (veja abaixo) |
| Campo | Tipo | Descrição |
|---|
sessionId | string | Identificador da sessão do Claude Code |
cwd | string | Diretório de trabalho da sessão do Claude Code |
transcriptPath | string | Caminho para o arquivo de transcrição JSONL da sessão |
Tipos de evento
| Evento | Quando dispara | Conteúdo de toolInput |
|---|
PreToolUse | Antes de Claude executar uma ferramenta | A entrada da ferramenta (ex.: { command: "..." } para Bash) |
PostToolUse | Após a conclusão de uma ferramenta | A entrada da ferramenta + tool_result (a saída) |
Notification | Quando Claude envia uma notificação | { message: "...", notification_type: "idle" | "permission_prompt" | ... } - hooks devem sempre retornar allow(), não podem bloquear notificações |
Stop | Quando a sessão Claude encerra | Vazio |
Ordem de avaliação
As políticas são avaliadas nesta ordem:
- Políticas embutidas (na ordem de definição)
- Políticas personalizadas explícitas do
customPoliciesPath (na ordem de .add())
- Políticas de convenção do projeto
.failproofai/policies/ (arquivos em ordem alfabética, ordem de .add() dentro de cada arquivo)
- Políticas de convenção do usuário
~/.failproofai/policies/ (arquivos em ordem alfabética, ordem de .add() dentro de cada arquivo)
O primeiro deny interrompe todas as políticas subsequentes. Todas as mensagens instruct são acumuladas e entregues juntas.
Importações transitivas
Arquivos de políticas personalizadas podem importar módulos locais usando caminhos relativos:
// my-policies.js
import { isBlockedPath } from "./utils.js";
import { checkApproval } from "./approval-client.js";
customPolicies.add({
name: "approval-gate",
fn: async (ctx) => {
if (ctx.toolName !== "Bash") return allow();
const approved = await checkApproval(ctx.toolInput?.command, ctx.session?.sessionId);
return approved ? allow() : deny("Approval required for this command");
},
});
Todas as importações relativas acessíveis a partir do arquivo de entrada são resolvidas. Isso é implementado reescrevendo as importações de from "failproofai" para o caminho real do dist e criando arquivos .mjs temporários para garantir compatibilidade com ESM.
Filtragem por tipo de evento
Use match.events para limitar quando uma política é disparada:
customPolicies.add({
name: "require-summary-on-stop",
match: { events: ["Stop"] },
fn: async (ctx) => {
// Dispara apenas quando a sessão encerra
// ctx.session.transcriptPath contém o log completo da sessão
return allow();
},
});
Omita match completamente para disparar em todos os tipos de evento.
Tratamento de erros e modos de falha
As políticas personalizadas são fail-open: erros nunca bloqueiam as políticas embutidas nem causam crash no handler de hook.
| Falha | Comportamento |
|---|
customPoliciesPath não definido | Nenhuma política personalizada explícita é executada; políticas de convenção e embutidas continuam normalmente |
| Arquivo não encontrado | Aviso registrado em ~/.failproofai/hook.log; políticas embutidas continuam |
| Erro de sintaxe/importação (explícito) | Erro registrado em ~/.failproofai/hook.log; políticas personalizadas explícitas são ignoradas |
| Erro de sintaxe/importação (convenção) | Erro registrado; aquele arquivo é ignorado, outros arquivos de convenção ainda são carregados |
fn lança erro em tempo de execução | Erro registrado; aquele hook é tratado como allow; outros hooks continuam |
fn demora mais de 10s | Timeout registrado; tratado como allow |
| Diretório de convenção ausente | Nenhuma política de convenção é executada; sem erro |
Para depurar erros de políticas personalizadas, monitore o arquivo de log:tail -f ~/.failproofai/hook.log
Exemplo completo: múltiplas políticas
// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";
// Impede o agente de escrever no diretório secrets/
customPolicies.add({
name: "block-secrets-dir",
description: "Prevent agent from writing to secrets/ directory",
match: { events: ["PreToolUse"] },
fn: async (ctx) => {
if (!["Write", "Edit"].includes(ctx.toolName ?? "")) return allow();
const path = ctx.toolInput?.file_path ?? "";
if (path.includes("secrets/")) return deny("Writing to secrets/ is not permitted");
return allow();
},
});
// Mantém o agente no caminho certo: verifica testes antes de commitar
customPolicies.add({
name: "remind-test-before-commit",
description: "Keep the agent on track: verify tests pass before committing",
match: { events: ["PreToolUse"] },
fn: async (ctx) => {
if (ctx.toolName !== "Bash") return allow();
const cmd = ctx.toolInput?.command ?? "";
if (/git\s+commit/.test(cmd)) {
return instruct("Verify all tests pass before committing. Run `bun test` if you haven't already.");
}
return allow();
},
});
// Previne alterações de dependências não planejadas durante o período de congelamento
customPolicies.add({
name: "dependency-freeze",
description: "Prevent unplanned dependency changes during freeze period",
match: { events: ["PreToolUse"] },
fn: async (ctx) => {
if (ctx.toolName !== "Bash") return allow();
const cmd = ctx.toolInput?.command ?? "";
const isInstall = /^(npm install|yarn add|bun add|pnpm add)\s+\S/.test(cmd);
if (isInstall && process.env.DEPENDENCY_FREEZE === "1") {
return deny("Package installs are frozen. Unset DEPENDENCY_FREEZE to allow.");
}
return allow();
},
});
export { customPolicies };
Exemplos
O diretório examples/ contém arquivos de políticas prontos para uso:
| Arquivo | Conteúdo |
|---|
examples/policies-basic.js | Cinco políticas iniciais cobrindo modos de falha comuns de agentes |
examples/policies-advanced/index.js | Padrões avançados: importações transitivas, chamadas assíncronas, limpeza de saída e hooks de fim de sessão |
examples/convention-policies/security-policies.mjs | Políticas de segurança baseadas em convenção (bloquear escritas em .env, impedir reescrita do histórico git) |
examples/convention-policies/workflow-policies.mjs | Políticas de fluxo de trabalho baseadas em convenção (lembretes de testes, auditoria de escritas em arquivos) |
failproofai policies --install --custom ./examples/policies-basic.js
Usando exemplos baseados em convenção
# Copiar para o nível do projeto
mkdir -p .failproofai/policies
cp examples/convention-policies/*.mjs .failproofai/policies/
# Ou copiar para o nível do usuário
mkdir -p ~/.failproofai/policies
cp examples/convention-policies/*.mjs ~/.failproofai/policies/
Nenhum comando de instalação é necessário — os arquivos são detectados automaticamente no próximo evento de hook.