カスタムポリシーを使えば、あらゆるエージェントの動作に対してルールを定義できます。プロジェクト規約の強制、ドリフトの防止、破壊的な操作のゲート制御、スタックしたエージェントの検出、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)
ポリシーを登録します。同一ファイル内で複数のポリシーを登録するために何度でも呼び出せます。
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に表示されます。一つの deny が発生すると、以降の評価はすべてスキップされます。
instruct(message) — メッセージは現在のツール呼び出しに対するClaudeのコンテキストに追記されます。すべての instruct メッセージは蓄積されてまとめて配信されます。
policyParams の hint フィールドを追加することで、deny や instruct のメッセージに追加のガイダンスを付け加えられます。コードの変更は不要です。これはカスタム(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/
インストールコマンドは不要です。次のフックイベント時に自動的にファイルが読み込まれます。