カスタムポリシーを使うと、あらゆるエージェントの動作に対してルールを記述できます。プロジェクト規約の強制、ドリフトの防止、破壊的操作のゲート処理、スタックしたエージェントの検出、Slackや承認ワークフローとの連携など、さまざまな用途に対応しています。組み込みポリシーと同じフックイベントシステムと allow、deny、instruct の判定を使用します。
クイックサンプル
// 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();
},
});
インストール:
failproofai policies --install --custom ./my-policies.js
カスタムポリシーの2つの読み込み方法
オプション1: 規約ベース(推奨)
*policies.{js,mjs,ts} ファイルを .failproofai/policies/ に配置するだけで自動的に読み込まれます。フラグや設定変更は不要です。gitフックと同じ仕組みで、ファイルを置くだけで動作します。
# プロジェクトレベル — gitにコミットしてチームで共有
.failproofai/policies/security-policies.mjs
.failproofai/policies/workflow-policies.mjs
# ユーザーレベル — 個人用、すべてのプロジェクトに適用
~/.failproofai/policies/my-policies.mjs
動作の仕組み:
- プロジェクトとユーザーの両ディレクトリがスキャンされます(和集合 — スコープ優先ではありません)
- ファイルは各ディレクトリ内でアルファベット順に読み込まれます。順序を制御するには
01-、02- などのプレフィックスを付けてください
*policies.{js,mjs,ts} にマッチするファイルのみ読み込まれ、それ以外は無視されます
- 各ファイルは独立して読み込まれます(ファイル単位でフェイルオープン)
- 明示的な
--custom や組み込みポリシーと共存できます
規約ポリシーは、組織の品質基準を構築する最も簡単な方法です。.failproofai/policies/ をgitにコミットすれば、開発者ごとのセットアップなしにすべてのチームメンバーが同じルールを自動的に適用できます。新しい障害パターンを発見したらポリシーを追加してプッシュするだけで、継続的に改善される生きた品質基準が積み上がっていきます。
オプション2: 明示的なファイルパス
# カスタムポリシーファイルを指定してインストール
failproofai policies --install --custom ./my-policies.js
# ポリシーファイルのパスを変更
failproofai policies --install --custom ./new-policies.js
# 設定からカスタムポリシーのパスを削除
failproofai policies --uninstall --custom
解決された絶対パスは policies-config.json の customPoliciesPath に保存されます。ファイルはフックイベントのたびに新たに読み込まれ、イベント間でのキャッシュはありません。
両方を組み合わせて使う
規約ポリシーと明示的な --custom ファイルは共存できます。読み込み順序:
- 明示的な
customPoliciesPath ファイル(設定されている場合)
- プロジェクト規約ファイル(
{cwd}/.failproofai/policies/、アルファベット順)
- ユーザー規約ファイル(
~/.failproofai/policies/、アルファベット順)
API
インポート
import { customPolicies, allow, deny, instruct } from "failproofai";
customPolicies.add(hook)
ポリシーを登録します。1つのファイルに複数のポリシーを定義する場合は、必要なだけ呼び出せます。
customPolicies.add({
name: string; // 必須 - 一意の識別子
description?: string; // `failproofai policies` の出力に表示
match?: { events?: HookEventType[] }; // イベントタイプでフィルタ。省略すると全イベントにマッチ
fn: (ctx: PolicyContext) => PolicyResult | Promise<PolicyResult>;
});
判定ヘルパー
| 関数 | 効果 | 使いどころ |
|---|
allow() | 操作をサイレントで許可 | アクションが安全でメッセージが不要な場合 |
deny(message) | 操作をブロック | エージェントがこのアクションを実行すべきでない場合 |
instruct(message) | ブロックせずにコンテキストを追加 | エージェントが正しい方向に進むための追加情報を渡す場合 |
deny(message) — メッセージは "Blocked by failproofai:" というプレフィックスが付いて Claude に表示されます。1つでも deny が返されると、それ以降の評価は短絡されます。
instruct(message) — メッセージは現在のツール呼び出しに対する Claude のコンテキストに追記されます。すべての instruct メッセージは蓄積されてまとめて配信されます。
deny や instruct のメッセージに追加のガイダンスを付け加えるには、policyParams の hint フィールドを使います — コードの変更は不要です。カスタム(custom/)、プロジェクト規約(.failproofai-project/)、ユーザー規約(.failproofai-user/)ポリシーでも機能します。詳細は 設定 → hint を参照してください。
情報提供用の allow メッセージ
allow(message) は操作を許可しつつ、Claude へ情報メッセージを送信します。メッセージはフックハンドラーの stdout レスポンスの additionalContext として配信されます — instruct と同じ仕組みですが、意味的には異なります: 警告ではなくステータス更新です。
| 関数 | 効果 | 使いどころ |
|---|
allow(message) | 許可しつつ Claude にコンテキストを送信 | チェックが通過したことの確認、またはチェックがスキップされた理由の説明 |
ユースケース:
- ステータス確認:
allow("All CI checks passed.") — すべて正常であることを Claude に伝える
- フェイルオープンの説明:
allow("GitHub CLI not installed, skipping CI check.") — チェックがスキップされた理由を Claude に伝え、完全なコンテキストを提供する
- 複数メッセージの蓄積: 複数のポリシーがそれぞれ
allow(message) を返した場合、すべてのメッセージが改行で結合されてまとめて配信される
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.");
// ... ブランチのステータスを確認 ...
if (allPushed) {
return allow("Branch is up to date with remote.");
}
return deny("Unpushed changes detected.");
},
});
PolicyContext フィールド
| フィールド | 型 | 説明 |
|---|
eventType | string | "PreToolUse"、"PostToolUse"、"Notification"、"Stop" |
toolName | string | undefined | 呼び出されるツール(例: "Bash"、"Write"、"Read") |
toolInput | Record<string, unknown> | undefined | ツールの入力パラメータ |
payload | Record<string, unknown> | Claude Code からの生のイベントペイロード |
session | SessionMetadata | undefined | セッションコンテキスト(下記参照) |
| フィールド | 型 | 説明 |
|---|
sessionId | string | Claude Code セッション識別子 |
cwd | string | Claude Code セッションの作業ディレクトリ |
transcriptPath | string | セッションの JSONL トランスクリプトファイルへのパス |
イベントタイプ
| イベント | 発火タイミング | toolInput の内容 |
|---|
PreToolUse | Claude がツールを実行する前 | ツールの入力(例: Bash の場合 { command: "..." }) |
PostToolUse | ツールの実行完了後 | ツールの入力 + tool_result(出力) |
Notification | Claude が通知を送信するとき | { message: "...", notification_type: "idle" | "permission_prompt" | ... } — フックは常に allow() を返す必要があり、通知をブロックすることはできません |
Stop | Claude セッションが終了するとき | 空 |
評価順序
ポリシーは以下の順序で評価されます:
- 組み込みポリシー(定義順)
customPoliciesPath からの明示的なカスタムポリシー(.add() の呼び出し順)
- プロジェクトの
.failproofai/policies/ からの規約ポリシー(ファイルはアルファベット順、ファイル内は .add() の呼び出し順)
- ユーザーの
~/.failproofai/policies/ からの規約ポリシー(ファイルはアルファベット順、ファイル内は .add() の呼び出し順)
最初の deny はそれ以降のすべてのポリシーを短絡します。すべての instruct メッセージは蓄積されてまとめて配信されます。
推移的なインポート
カスタムポリシーファイルは相対パスを使ってローカルモジュールをインポートできます:
// 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");
},
});
エントリファイルから到達可能なすべての相対インポートが解決されます。これは from "failproofai" インポートを実際の dist パスに書き換え、ESM 互換性を確保するために一時的な .mjs ファイルを作成することで実装されています。
イベントタイプのフィルタリング
match.events を使って、ポリシーが発火するタイミングを限定できます:
customPolicies.add({
name: "require-summary-on-stop",
match: { events: ["Stop"] },
fn: async (ctx) => {
// セッション終了時のみ発火
// ctx.session.transcriptPath にセッションの全ログが含まれる
return allow();
},
});
match を完全に省略すると、すべてのイベントタイプで発火します。
エラー処理と障害モード
カスタムポリシーはフェイルオープンです。エラーが発生しても組み込みポリシーをブロックしたり、フックハンドラーをクラッシュさせたりすることはありません。
| 障害 | 動作 |
|---|
customPoliciesPath が未設定 | 明示的なカスタムポリシーは実行されず、規約ポリシーと組み込みポリシーは通常通り継続 |
| ファイルが見つからない | ~/.failproofai/hook.log に警告を記録し、組み込みポリシーは継続 |
| 構文/インポートエラー(明示的) | ~/.failproofai/hook.log にエラーを記録し、明示的なカスタムポリシーをスキップ |
| 構文/インポートエラー(規約) | エラーを記録し、そのファイルをスキップ。他の規約ファイルは引き続き読み込まれる |
fn が実行時に例外をスロー | エラーを記録し、そのフックを allow として扱い、他のフックは継続 |
fn が10秒以上かかる | タイムアウトを記録し、allow として扱う |
| 規約ディレクトリが存在しない | 規約ポリシーは実行されず、エラーなし |
カスタムポリシーのエラーをデバッグするには、ログファイルを監視してください:tail -f ~/.failproofai/hook.log
全サンプル: 複数ポリシー
// my-policies.js
import { customPolicies, allow, deny, instruct } from "failproofai";
// エージェントが 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();
},
});
// エージェントを正しい方向に維持: コミット前にテストを確認
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();
},
});
// フリーズ期間中の計画外の依存関係変更を防ぐ
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 };
サンプル集
examples/ ディレクトリにはすぐに使えるポリシーファイルが含まれています:
| ファイル | 内容 |
|---|
examples/policies-basic.js | 一般的なエージェント障害モードをカバーする5つのスターターポリシー |
examples/policies-advanced/index.js | 高度なパターン: 推移的なインポート、非同期呼び出し、出力のスクラビング、セッション終了フック |
examples/convention-policies/security-policies.mjs | 規約ベースのセキュリティポリシー(.env ファイルへの書き込みのブロック、git 履歴の書き換え防止) |
examples/convention-policies/workflow-policies.mjs | 規約ベースのワークフローポリシー(テストリマインダー、ファイル書き込みの監査) |
明示的なファイルサンプルの使い方
failproofai policies --install --custom ./examples/policies-basic.js
規約ベースのサンプルの使い方
# プロジェクトレベルにコピー
mkdir -p .failproofai/policies
cp examples/convention-policies/*.mjs .failproofai/policies/
# またはユーザーレベルにコピー
mkdir -p ~/.failproofai/policies
cp examples/convention-policies/*.mjs ~/.failproofai/policies/
インストールコマンドは不要です。次のフックイベント時に自動的にファイルが読み込まれます。