Passer au contenu principal
Les politiques personnalisées vous permettent d’écrire des règles pour n’importe quel comportement d’agent : appliquer des conventions de projet, prévenir la dérive, bloquer les opérations destructrices, détecter les agents bloqués, ou s’intégrer avec Slack, des workflows d’approbation, et plus encore. Elles utilisent le même système d’événements de hook et les mêmes décisions allow, deny, instruct que les politiques intégrées.

Exemple rapide

// 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();
  },
});
Installez-le :
failproofai policies --install --custom ./my-policies.js

Deux façons de charger des politiques personnalisées

Option 1 : Par convention (recommandée)

Déposez des fichiers *policies.{js,mjs,ts} dans .failproofai/policies/ et ils sont chargés automatiquement — aucun indicateur ni modification de configuration nécessaire. Cela fonctionne comme les hooks git : déposez un fichier, ça marche tout seul.
# Niveau projet — commis dans git, partagé avec l'équipe
.failproofai/policies/security-policies.mjs
.failproofai/policies/workflow-policies.mjs

# Niveau utilisateur — personnel, s'applique à tous les projets
~/.failproofai/policies/my-policies.mjs
Comment ça fonctionne :
  • Les répertoires projet et utilisateur sont tous deux analysés (union — pas de priorité au premier scope trouvé)
  • Les fichiers sont chargés par ordre alphabétique dans chaque répertoire. Préfixez avec 01-, 02- pour contrôler l’ordre
  • Seuls les fichiers correspondant à *policies.{js,mjs,ts} sont chargés ; les autres fichiers sont ignorés
  • Chaque fichier est chargé indépendamment (fail-open par fichier)
  • Fonctionne conjointement avec --custom explicite et les politiques intégrées
Les politiques par convention sont le moyen le plus simple d’établir un standard de qualité pour votre organisation. Commitez .failproofai/policies/ dans git et chaque membre de l’équipe obtient automatiquement les mêmes règles — aucune configuration par développeur nécessaire. Au fur et à mesure que votre équipe découvre de nouveaux modes d’échec, ajoutez une politique et poussez. Ces politiques deviennent au fil du temps un standard de qualité vivant qui s’améliore à chaque contribution.

Option 2 : Chemin de fichier explicite

# Installer avec un fichier de politiques personnalisées
failproofai policies --install --custom ./my-policies.js

# Remplacer le chemin du fichier de politiques
failproofai policies --install --custom ./new-policies.js

# Supprimer le chemin de politiques personnalisées de la configuration
failproofai policies --uninstall --custom
Le chemin absolu résolu est stocké dans policies-config.json sous la clé customPoliciesPath. Le fichier est chargé à nouveau à chaque événement de hook - il n’y a pas de mise en cache entre les événements.

Utiliser les deux ensemble

Les politiques par convention et le fichier --custom explicite peuvent coexister. Ordre de chargement :
  1. Fichier customPoliciesPath explicite (si configuré)
  2. Fichiers de convention du projet ({cwd}/.failproofai/policies/, alphabétique)
  3. Fichiers de convention utilisateur (~/.failproofai/policies/, alphabétique)

API

Import

import { customPolicies, allow, deny, instruct } from "failproofai";

customPolicies.add(hook)

Enregistre une politique. Appelez cette méthode autant de fois que nécessaire pour plusieurs politiques dans le même fichier.
customPolicies.add({
  name: string;                         // requis - identifiant unique
  description?: string;                 // affiché dans la sortie de `failproofai policies`
  match?: { events?: HookEventType[] }; // filtrer par type d'événement ; omettez pour correspondre à tous
  fn: (ctx: PolicyContext) => PolicyResult | Promise<PolicyResult>;
});

Fonctions d’aide aux décisions

FonctionEffetÀ utiliser quand
allow()Autorise l’opération silencieusementL’action est sûre, aucun message nécessaire
deny(message)Bloque l’opérationL’agent ne devrait pas effectuer cette action
instruct(message)Ajoute du contexte sans bloquerDonne à l’agent un contexte supplémentaire pour rester sur la bonne voie
deny(message) - le message apparaît dans Claude précédé de "Blocked by failproofai:". Un seul deny court-circuite toute évaluation ultérieure. instruct(message) - le message est ajouté au contexte de Claude pour l’appel d’outil en cours. Tous les messages instruct sont accumulés et délivrés ensemble.
Vous pouvez ajouter des conseils supplémentaires à n’importe quel message deny ou instruct en ajoutant un champ hint dans policyParams — aucune modification de code nécessaire. Cela fonctionne également pour les politiques personnalisées (custom/), de convention projet (.failproofai-project/) et de convention utilisateur (.failproofai-user/). Voir Configuration → hint pour plus de détails.

Messages allow informationnels

allow(message) autorise l’opération et envoie un message informationnel à Claude. Le message est délivré en tant que additionalContext dans la réponse stdout du gestionnaire de hook — le même mécanisme que instruct, mais sémantiquement différent : c’est une mise à jour de statut, pas un avertissement.
FonctionEffetÀ utiliser quand
allow(message)Autorise et envoie du contexte à ClaudeConfirmer qu’une vérification a réussi, ou expliquer pourquoi elle a été ignorée
Cas d’utilisation :
  • Confirmations de statut : allow("All CI checks passed.") — indique à Claude que tout est au vert
  • Explications fail-open : allow("GitHub CLI not installed, skipping CI check.") — indique à Claude pourquoi une vérification a été ignorée pour qu’il dispose du contexte complet
  • Accumulation de plusieurs messages : si plusieurs politiques retournent chacune allow(message), tous les messages sont joints avec des sauts de ligne et délivrés ensemble
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.");
  },
});

Champs de PolicyContext

ChampTypeDescription
eventTypestring"PreToolUse", "PostToolUse", "Notification", "Stop"
toolNamestring | undefinedL’outil appelé (ex. "Bash", "Write", "Read")
toolInputRecord<string, unknown> | undefinedLes paramètres d’entrée de l’outil
payloadRecord<string, unknown>Charge utile brute complète de l’événement provenant de Claude Code
sessionSessionMetadata | undefinedContexte de session (voir ci-dessous)

Champs de SessionMetadata

ChampTypeDescription
sessionIdstringIdentifiant de session Claude Code
cwdstringRépertoire de travail de la session Claude Code
transcriptPathstringChemin vers le fichier de transcription JSONL de la session

Types d’événements

ÉvénementQuand il se déclencheContenu de toolInput
PreToolUseAvant que Claude exécute un outilL’entrée de l’outil (ex. { command: "..." } pour Bash)
PostToolUseAprès qu’un outil s’est terminéL’entrée de l’outil + tool_result (la sortie)
NotificationQuand Claude envoie une notification{ message: "...", notification_type: "idle" | "permission_prompt" | ... } - les hooks doivent toujours retourner allow(), ils ne peuvent pas bloquer les notifications
StopQuand la session Claude se termineVide

Ordre d’évaluation

Les politiques sont évaluées dans cet ordre :
  1. Politiques intégrées (dans l’ordre de définition)
  2. Politiques personnalisées explicites depuis customPoliciesPath (dans l’ordre des .add())
  3. Politiques de convention du projet .failproofai/policies/ (fichiers alphabétiques, ordre .add() à l’intérieur)
  4. Politiques de convention utilisateur ~/.failproofai/policies/ (fichiers alphabétiques, ordre .add() à l’intérieur)
Le premier deny court-circuite toutes les politiques suivantes. Tous les messages instruct sont accumulés et délivrés ensemble.

Imports transitifs

Les fichiers de politiques personnalisées peuvent importer des modules locaux en utilisant des chemins relatifs :
// 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");
  },
});
Tous les imports relatifs accessibles depuis le fichier d’entrée sont résolus. Cela est implémenté en réécrivant les imports from "failproofai" vers le chemin dist réel et en créant des fichiers .mjs temporaires pour assurer la compatibilité ESM.

Filtrage par type d’événement

Utilisez match.events pour limiter le déclenchement d’une politique :
customPolicies.add({
  name: "require-summary-on-stop",
  match: { events: ["Stop"] },
  fn: async (ctx) => {
    // Se déclenche uniquement quand la session se termine
    // ctx.session.transcriptPath contient le journal complet de la session
    return allow();
  },
});
Omettez match entièrement pour se déclencher sur chaque type d’événement.

Gestion des erreurs et modes d’échec

Les politiques personnalisées sont fail-open : les erreurs ne bloquent jamais les politiques intégrées et ne font pas planter le gestionnaire de hook.
ÉchecComportement
customPoliciesPath non définiAucune politique personnalisée explicite ne s’exécute ; les politiques de convention et les politiques intégrées continuent normalement
Fichier introuvableAvertissement enregistré dans ~/.failproofai/hook.log ; les politiques intégrées continuent
Erreur de syntaxe/import (explicite)Erreur enregistrée dans ~/.failproofai/hook.log ; les politiques personnalisées explicites sont ignorées
Erreur de syntaxe/import (convention)Erreur enregistrée ; ce fichier est ignoré, les autres fichiers de convention se chargent quand même
fn lève une exception à l’exécutionErreur enregistrée ; ce hook est traité comme allow ; les autres hooks continuent
fn prend plus de 10 secondesTimeout enregistré ; traité comme allow
Répertoire de convention manquantAucune politique de convention ne s’exécute ; pas d’erreur
Pour déboguer les erreurs de politiques personnalisées, surveillez le fichier de log :
tail -f ~/.failproofai/hook.log

Exemple complet : plusieurs politiques

// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";

// Prevent agent from writing to secrets/ directory
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();
  },
});

// Keep the agent on track: verify tests before committing
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();
  },
});

// Prevent unplanned dependency changes during freeze
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 };

Exemples

Le répertoire examples/ contient des fichiers de politiques prêts à l’emploi :
FichierContenu
examples/policies-basic.jsCinq politiques de démarrage couvrant les modes d’échec courants des agents
examples/policies-advanced/index.jsModèles avancés : imports transitifs, appels asynchrones, nettoyage des sorties et hooks de fin de session
examples/convention-policies/security-policies.mjsPolitiques de sécurité par convention (bloquer les écritures dans .env, empêcher la réécriture de l’historique git)
examples/convention-policies/workflow-policies.mjsPolitiques de workflow par convention (rappels de tests, audit des écritures de fichiers)

Utiliser les exemples de fichiers explicites

failproofai policies --install --custom ./examples/policies-basic.js

Utiliser les exemples par convention

# Copier au niveau du projet
mkdir -p .failproofai/policies
cp examples/convention-policies/*.mjs .failproofai/policies/

# Ou copier au niveau utilisateur
mkdir -p ~/.failproofai/policies
cp examples/convention-policies/*.mjs ~/.failproofai/policies/
Aucune commande d’installation nécessaire — les fichiers sont pris en compte automatiquement au prochain événement de hook.