Configuration scopes
There are three configuration scopes, evaluated in priority order:| Scope | File path | Purpose |
|---|---|---|
| project | .failproofai/policies-config.json | Per-repo settings, committed to version control |
| local | .failproofai/policies-config.local.json | Personal per-repo overrides, gitignored |
| global | ~/.failproofai/policies-config.json | User-level defaults across all projects |
Merge rules
enabledPolicies - the union of all three scopes. A policy enabled at any level is active.
policyParams - first scope that defines params for a given policy wins entirely. There is no deep merging of values within a policy’s params.
customPoliciesPath - first scope that defines it wins.
llm - first scope that defines it wins.
Config file format
Field reference
enabledPolicies
Type: string[]
List of policy names to enable. Names must match exactly the policy identifiers shown by failproofai policies. See Built-in Policies for the full list.
Policies not in enabledPolicies are inactive, even if they have entries in policyParams.
policyParams
Type: Record<string, Record<string, unknown>>
Per-policy parameter overrides. The outer key is the policy name; the inner keys are policy-specific. Each policy documents its available parameters in Built-in Policies.
If a policy has parameters but you don’t specify them, the policy’s built-in defaults are used. Users who do not configure policyParams at all get identical behavior to previous versions.
Unknown keys inside a policy’s params block are silently ignored at hook-fire time but flagged as warnings when you run failproofai policies.
hint (cross-cutting)
Type: string (optional)
A message appended to the reason when a policy returns deny or instruct. Use it to give Claude actionable guidance without modifying the policy itself.
Works with any policy type — built-in, custom (custom/), project convention (.failproofai-project/), or user convention (.failproofai-user/).
block-force-push denies, Claude sees: “Force-pushing is blocked. Try creating a fresh branch instead.”
Non-string values and empty strings are silently ignored. If hint is not set, behavior is unchanged (backward-compatible).
customPoliciesPath
Type: string (absolute path)
Path to a JavaScript file containing custom hook policies. This is set automatically by failproofai policies --install --custom <path> (the path is resolved to absolute before being stored).
The file is loaded fresh on every hook event - there is no caching. See Custom Policies for authoring details.
Convention-based policies
In addition to the explicitcustomPoliciesPath, failproofai automatically discovers and loads policy files from .failproofai/policies/ directories:
| Level | Directory | Scope |
|---|---|---|
| Project | .failproofai/policies/ | Shared with team via version control |
| User | ~/.failproofai/policies/ | Personal, applies to all projects |
*policies.{js,mjs,ts} are loaded (e.g. security-policies.mjs, workflow-policies.js). Other files in the directory are ignored.
No config needed: Convention policies require no entries in policies-config.json. Just drop files into the directory and they’re picked up on the next hook event.
Union loading: Both project and user convention directories are scanned. All matching files from both levels are loaded (unlike customPoliciesPath which uses first-scope-wins).
See Custom Policies for more details and examples.
llm
Type: object (optional)
LLM client configuration for policies that make AI calls. Not required for most setups.
Managing configuration from the CLI
Thepolicies --install and policies --uninstall commands write to your agent CLI’s hook settings file (the hook entry points), while policies-config.json is the file you manage directly. The two are separate:
- Agent CLI settings — tells the agent to call
failproofai --hook <event>on each tool use:- Claude Code:
~/.claude/settings.json(user),<cwd>/.claude/settings.json(project),<cwd>/.claude/settings.local.json(local) - OpenAI Codex:
~/.codex/hooks.json(user),<cwd>/.codex/hooks.json(project) — Codex doesn’t have alocalscope - GitHub Copilot CLI (beta):
~/.copilot/hooks/failproofai.json(user),<cwd>/.github/hooks/failproofai.json(project) — Copilot has nolocalscope. Hook entries use Copilot’s OS-keyedbash/powershellcommand fields withtimeoutSec; the file carries a top-levelversion: 1marker. Copilot CLI support is beta while we verify theevents.jsonlrecord schema (which the public docs do not specify) against more real-world sessions. - Cursor Agent (beta):
~/.cursor/hooks.json(user),<cwd>/.cursor/hooks.json(project) — Cursor has nolocalscope. Hook entries use the Claude-shaped{type, command, timeout}form (nobash/powershellsplit), but stored under camelCase event keys (preToolUse,beforeSubmitPrompt, …) in a flat array per Cursor’s hooks schema; the file carries a top-levelversion: 1marker. The handler canonicalizes camelCase → PascalCase viaCURSOR_EVENT_MAPso existing builtin policies fire unchanged. Cursor Agent support is beta while we verify Cursor’s transcript on-disk format (not specified in the public docs) against more real-world installs. - OpenCode (beta):
~/.config/opencode/opencode.json+~/.config/opencode/plugins/failproofai.mjs(user),<cwd>/.opencode/opencode.json+<cwd>/.opencode/plugins/failproofai.mjs(project) — OpenCode has nolocalscope. Unlike the other six CLIs, OpenCode has no external-command hook system: it loads in-process JS/TS plugins explicitly registered via theplugin: []array inopencode.json(auto-discovery from.opencode/plugins/is not how plugins load on opencode v1.14.33). Install drops a small generated plugin shim that subprocess-calls the failproofai binary and translates the binary’s Claude-shape JSON response back into plugin semantics:throw new Error()for tool-event deny (cancels the tool call),client.session.prompt(...)for instruct AND forStop/SubagentStopdeny (submits the deny reason as the next user message — the only force-retry channel sincesession.idleis notification-only and throwing from it is a no-op), and no-op for allow. The shim canonicalizes both tool names (lowercase → PascalCase viaOPENCODE_TOOL_MAP) and tool-input arg keys (camelCase → snake_case viaOPENCODE_TOOL_INPUT_MAPforRead/Write/Edit, e.g.filePath→file_path,oldString→old_string) before forwarding to the binary, so path-checking builtins likeblock-read-outside-cwd,block-env-files, andblock-secrets-writefire unchanged on OpenCode tool calls. Sessions live in opencode’s SQLite DB at~/.local/share/opencode/opencode.db; the dashboard’s session viewer reads them viaopencode db --format jsonandopencode export <id>. OpenCode support is beta while we verify behavior across versions and against more real-world sessions. See the OpenCode plugins docs. - Pi (beta):
~/.pi/agent/settings.json(user),<cwd>/.pi/settings.json(project) — Pi has nolocalscope. Pi loads TypeScript extension packages at startup; the settings file is a flat string array{"packages": ["./relative/path", …]}. failproofai writes a single packages-array entry pointing at its bundledpi-extension/directory. The extension internally subscribes to Pi’stool_call/user_bash/input/session_startevents and shells out tofailproofai --hook <Event> --cli pi; the handler canonicalizes underscore_lower_snake_case → PascalCase viaPI_EVENT_MAPso existing builtin policies fire unchanged. Tool input args are also canonicalized viaPI_TOOL_INPUT_MAP(Pi’s Read / Write / Edit deliverpathrather thanfile_path; mapping the top-level key letsblock-env-filesandblock-secrets-writefire —block-read-outside-cwdalready had apathfallback). Pi support is beta while Pi’s extension API and session-log layout stabilize. - Gemini CLI (beta):
~/.gemini/settings.json(user),<cwd>/.gemini/settings.json(project) — Gemini has nolocalscope (it documents asystemscope at/etc/gemini-cli/settings.jsonwhich failproofai does not expose). Hook entries use Claude’s{type, command, timeout}form wrapped in Gemini’s{matcher, hooks: [...]}matcher schema withmatcher: "*"by default. Events are PascalCase (SessionStart,BeforeAgent,AfterAgent,BeforeModel,AfterModel,BeforeToolSelection,BeforeTool,AfterTool,PreCompress,Notification,SessionEnd); the handler maps to Claude canonical names viaGEMINI_EVENT_MAP. Tool names are snake_case (run_shell_command,read_file,write_file,replace, …) — the handler canonicalizes viaGEMINI_TOOL_MAPso existing builtin policies fire unchanged. The policy evaluator emits Gemini’s flat{decision: "deny", reason}shape (preferred per Gemini’s “Golden Rule” exit-0 contract),{hookSpecificOutput: {hookEventName, additionalContext}}for context injection on BeforeAgent / AfterTool / SessionStart, and{decision: "block", reason}on AfterAgent for force-retry semantics. Gemini CLI support is beta while we widen real-world coverage. See the Gemini CLI hooks docs.
- Claude Code:
policies-config.json— tells failproofai which policies to evaluate and with what params (shared across all agent CLIs)
--cli claude|codex|copilot|cursor|opencode|pi|gemini to target a specific agent (space-separated or repeated for any subset):
--cli is omitted, failproofai detects which agent CLIs are installed (which claude / which codex / which copilot / which cursor-agent / which opencode / which pi / which gemini):
- One CLI detected — auto-selects that CLI without prompting.
- Multiple CLIs detected in an interactive terminal — shows an arrow-key single-select prompt grouped into a
Detected (N)section (with anInstall for all N detectedaggregate row + each detected CLI individually) and aNot installed (M) · install hooks ahead of timesection listing every undetected supported CLI as a forward-install option (↑↓ to move, Enter to select, ^C to quit). The uninstall flow shows only the Detected section. - Multiple CLIs detected in a non-interactive run (CI, no TTY) — installs for all detected CLIs without prompting.
- None detected — falls back to
claude, with a warning that no agent binary was found in PATH; the hook command is still written so it activates as soon as you install one.
policies-config.json directly at any time; changes take effect immediately on the next hook event with no restart needed.
Example: project-level config with team defaults
Commit.failproofai/policies-config.json to your repo:
.failproofai/policies-config.local.json (gitignored) for personal overrides without affecting teammates.
