Overview
failproofai has two independent subsystems:- Hook handler - A fast CLI subprocess that Claude Code invokes on every agent tool call. Evaluates policies and returns a decision.
- Agent Monitor (Dashboard) - A Next.js web application for monitoring agent sessions and managing policies.
~/.failproofai/ and the project’s .failproofai/ directory, but they run as separate processes and communicate only through the filesystem.
Hook handler
Integration with Claude Code
When you runfailproofai policies --install, it writes entries like this into ~/.claude/settings.json:
failproofai --hook PreToolUse as a subprocess before each tool call, passing a JSON payload on stdin.
Payload format
PostToolUse events, the payload also contains tool_result with the tool’s output.
The handler enforces a 1 MB stdin limit. Payloads exceeding this are discarded and all policies implicitly allow.
Response format
Deny (PreToolUse):- Exit code:
2 - Reason written to stderr (not stdout)
- Exit code:
0 - Empty stdout
Processing pipeline
src/hooks/handler.ts implements the full pipeline:
Configuration loading
src/hooks/hooks-config.ts implements three-scope config loading.
enabledPolicies- deduplicated union across all three filespolicyParams- per-policy key, first file that defines it wins entirelycustomPoliciesPath- first file that defines it winsllm- first file that defines it wins
readHooksConfig() (global only) for reading and writing, since it is not invoked with a project cwd.
Policy evaluation
src/hooks/policy-evaluator.ts runs policies in order.
For each policy:
- Look up the policy’s
paramsschema (if it has one). - Read
policyParams[policy.name]from the merged config. - Merge user-provided values over schema defaults to produce
ctx.params. - Call
policy.fn(ctx)with the resolved context. - If the result is
deny, stop immediately and return that decision. - If the result is
instruct, accumulate the message and continue. - If the result is
allow, continue to the next policy.
- If any
denywas returned, emit the deny response. - If any
instructreturns were collected, emit a single instruct response with all messages joined. - Otherwise, emit an allow response (empty stdout, exit 0).
Builtin policies
src/hooks/builtin-policies.ts defines all 26 built-in policies as BuiltinPolicyDefinition objects:
params declare a PolicyParamsSchema with types and defaults for each parameter. The policy evaluator injects resolved values into ctx.params before calling fn. Policy functions read ctx.params without null-guarding because defaults are always applied first.
Pattern matching inside policies uses parsed command tokens (argv), not raw string matching. This prevents bypass via shell operator injection (e.g. a pattern for sudo systemctl status * cannot be bypassed by appending ; rm -rf / to the command).
Custom hooks
src/hooks/custom-hooks-registry.ts implements a globalThis-backed registry:
src/hooks/custom-hooks-loader.ts loads the user’s hooks file:
- Read
customPoliciesPathfrom config; skip if absent. - Resolve to absolute path; check file exists.
- Rewrite all
from "failproofai"imports to the actual dist path socustomPoliciesresolves to the sameglobalThisregistry. - Recursively rewrite transitive local imports to ensure ESM compatibility.
- Write temporary
.mjsfiles andimport()the entry file. - Call
getCustomHooks()to retrieve registered hooks. - Clean up all temp files in a
finallyblock.
~/.failproofai/hook.log and the loader returns an empty array. Built-in policies are unaffected.
Custom hooks are evaluated after all built-in policies. A custom hook deny still short-circuits further custom hooks (but all built-ins have already run by that point).
Activity logging
After each hook event, the handler appends a JSONL line to~/.failproofai/hook-activity.jsonl:
Dashboard architecture
The dashboard is a Next.js 16 application using the App Router with React Server Components and Server Actions.- Page components call
lib/projects.tsandlib/log-entries.tsto read project/session data directly from the filesystem (no API layer for reads). - The Policies page uses Server Actions for all mutations (toggle, params update, install/remove).
- The session viewer parses Claude’s JSONL transcript format and renders a timeline of messages and tool calls.
- No database - all persistent state is in plain files (
~/.failproofai/,~/.claude/projects/). - Server Actions for mutations - no REST API needed for CRUD operations.
- React Server Components for read pages - faster initial load, no client bundle for data fetching.
- Client components only where interactivity is needed (policy toggles, activity search, log viewer).

