Skip to main content
Custom policies let you write rules for any agent behavior: enforce project conventions, prevent drift, gate destructive operations, detect stuck agents, or integrate with Slack, approval workflows, and more. They use the same hook event system and allow, deny, instruct decisions as built-in policies.

Quick example

Install it:

Two ways to load custom policies

Drop *policies.{js,mjs,ts} files into .failproofai/policies/ and they’re automatically loaded — no flags or config changes needed. This works like git hooks: drop a file, it just works.
How it works:
  • Both project and user directories are scanned (union — not first-scope-wins)
  • Files are loaded alphabetically within each directory. Prefix with 01-, 02- to control order
  • Only files matching *policies.{js,mjs,ts} are loaded; other files are ignored
  • Each file is loaded independently (fail-open per file)
  • Works alongside explicit --custom and built-in policies
Convention policies are the easiest way to build a quality standard for your org. Commit .failproofai/policies/ to git and every team member gets the same rules automatically — no per-developer setup needed. As your team discovers new failure modes, add a policy and push. Over time these become a living quality standard that keeps improving with every contribution.

Option 2: Explicit file path

The resolved absolute path is stored in policies-config.json as customPoliciesPath. The file is loaded fresh on every hook event - there is no caching between events.

Using both together

Convention policies and the explicit --custom file can coexist. Load order:
  1. Explicit customPoliciesPath file (if configured)
  2. Project convention files ({cwd}/.failproofai/policies/, alphabetical)
  3. User convention files (~/.failproofai/policies/, alphabetical)

API

Import

customPolicies.add(hook)

Registers a policy. Call this as many times as needed for multiple policies in the same file.

Decision helpers

deny(message) - the message appears to Claude prefixed with "Blocked by failproofai:". A single deny short-circuits all further evaluation. instruct(message) - the message is appended to Claude’s context for the current tool call. All instruct messages are accumulated and delivered together.
You can append extra guidance to any deny or instruct message by adding a hint field in policyParams — no code change needed. This works for custom (custom/), project convention (.failproofai-project/), and user convention (.failproofai-user/) policies too. See Configuration → hint for details.

Informational allow messages

allow(message) permits the operation and sends an informational message back to Claude. The message is delivered as additionalContext in the hook handler’s stdout response — the same mechanism used by instruct, but semantically different: it’s a status update, not a warning. Use cases:
  • Status confirmations: allow("All CI checks passed.") — tells Claude everything is green
  • Fail-open explanations: allow("GitHub CLI not installed, skipping CI check.") — tells Claude why a check was skipped so it has full context
  • Multiple messages accumulate: if several policies each return allow(message), all messages are joined with newlines and delivered together

PolicyContext fields

SessionMetadata fields

Event types


Evaluation order

Policies are evaluated in this order:
  1. Built-in policies (in definition order)
  2. Explicit custom policies from customPoliciesPath (in .add() order)
  3. Convention policies from project .failproofai/policies/ (files alphabetical, .add() order within)
  4. Convention policies from user ~/.failproofai/policies/ (files alphabetical, .add() order within)
The first deny short-circuits all subsequent policies. All instruct messages are accumulated and delivered together.

Transitive imports

Custom policy files can import local modules using relative paths:
All relative imports reachable from the entry file are resolved. This is implemented by rewriting from "failproofai" imports to the actual dist path and creating temporary .mjs files to ensure ESM compatibility.

Event type filtering

Use match.events to limit when a policy fires:
Omit match entirely to fire on every event type.

Error handling and failure modes

Custom policies are fail-open: errors never block built-in policies or crash the hook handler.
To debug custom policy errors, watch the log file:

Full example: multiple policies


Examples

The examples/ directory contains ready-to-run policy files:

Using explicit file examples

Using convention-based examples

No install command needed — the files are picked up automatically on the next hook event.