メインコンテンツへスキップ
このドキュメントでは、failproofai の内部動作を説明します。フックシステムがエージェントのツール呼び出しをどのようにインターセプトするか、設定がどのように読み込まれてマージされるか、ポリシーがどのように評価されるか、そしてダッシュボードがエージェントのアクティビティをどのように監視するかについて解説します。

概要

failproofai には独立した 2 つのサブシステムがあります。
  1. フックハンドラー - Claude Code がエージェントのツール呼び出しごとに起動する高速な CLI サブプロセス。ポリシーを評価して判断を返します。
  2. エージェントモニター(ダッシュボード) - エージェントセッションの監視とポリシー管理のための Next.js ウェブアプリケーション。
両サブシステムは ~/.failproofai/ とプロジェクトの .failproofai/ ディレクトリにある設定ファイルを共有しますが、それぞれ独立したプロセスとして動作し、ファイルシステムを通じてのみ通信します。

フックハンドラー

Claude Code との連携

failproofai policies --install を実行すると、~/.claude/settings.json に以下のようなエントリが書き込まれます。
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
Claude Code はツール呼び出しごとに failproofai --hook PreToolUse をサブプロセスとして起動し、JSON ペイロードを stdin で渡します。

ペイロードの形式

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
PostToolUse イベントの場合、ペイロードにはツールの出力を含む tool_result も含まれます。 ハンドラーは stdin の上限を 1 MB に制限しています。この上限を超えるペイロードは破棄され、すべてのポリシーが暗黙的に allow となります。

レスポンスの形式

Deny(PreToolUse):
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
Deny(PostToolUse):
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
Instruct(Stop 以外のイベント):
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
Stop イベントの instruct:
  • 終了コード:2
  • 理由は stdout ではなく stderr に書き込まれます
Allow:
  • 終了コード:0
  • stdout は空
メッセージ付き Allow: allow(message) を使うと、操作が許可された場合でもポリシーから Claude に情報コンテキストを返すことができます。フックハンドラーは以下の JSON を stdout に書き込みます(設定ファイルへの書き込みではなく、deny や instruct と同様に Claude Code へのハンドラーレスポンスです)。
// フックハンドラープロセスが stdout に書き込む内容
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
  • 終了コード:0(操作は許可される)
  • 複数のポリシーがメッセージ付きで allow を返した場合、それらのメッセージは改行で結合されて単一の additionalContext 文字列になります
  • いずれのポリシーもメッセージを提供しない場合、stdout は空(従来と同じ動作)

処理パイプライン

src/hooks/handler.ts がパイプライン全体を実装しています。
stdin JSON
  → ペイロードのパース(最大 1 MB)
  → セッションメタデータの抽出(session_id、cwd、tool_name、tool_input など)
  → readMergedHooksConfig(cwd)    ← プロジェクト + ローカル + グローバル設定をマージ
  → 有効な組み込みポリシーをパラメーター解決後に登録
  → customPoliciesPath からカスタムポリシーを読み込み(設定されている場合)
  → カスタムポリシーをポリシーレジストリに登録
  → すべてのポリシーを評価(組み込みを先に、その後カスタムを)
      → 最初の deny で短絡
      → instruct の判断は蓄積
      → allow メッセージは蓄積
  → JSON 判断を stdout に書き込み
  → イベントを ~/.failproofai/hook-activity.jsonl に永続化
  → 終了
LLM 呼び出しなしで、典型的なペイロードの処理は 100ms 以内に完了します。

設定の読み込み

src/hooks/hooks-config.ts が 3 スコープの設定読み込みを実装しています。
[1] {cwd}/.failproofai/policies-config.json        ← プロジェクト(最高優先度)
[2] {cwd}/.failproofai/policies-config.local.json  ← ローカル
[3] ~/.failproofai/policies-config.json             ← グローバル(最低優先度)
マージのロジック:
  • enabledPolicies - 3 つのファイルにわたって重複排除した和集合
  • policyParams - ポリシーキーごとに、最初に定義したファイルが完全に優先
  • customPoliciesPath - 最初に定義したファイルが優先
  • llm - 最初に定義したファイルが優先
ウェブダッシュボードはプロジェクトの cwd を持たないため、読み取りと書き込みには readHooksConfig()(グローバルのみ)を使用します。

ポリシーの評価

src/hooks/policy-evaluator.ts がポリシーを順番に実行します。 各ポリシーについて:
  1. ポリシーの params スキーマを参照します(存在する場合)。
  2. マージ済み設定から policyParams[policy.name] を読み取ります。
  3. ユーザー指定の値をスキーマのデフォルト値にマージして ctx.params を生成します。
  4. 解決されたコンテキストを使って policy.fn(ctx) を呼び出します。
  5. 結果が deny の場合、即座に停止してその判断を返します。
  6. 結果が instruct の場合、メッセージを蓄積して処理を続行します。
  7. 結果が allow の場合、次のポリシーへ進みます。
すべてのポリシーの実行後:
  • deny が返された場合、deny レスポンスを出力します。
  • instruct が収集された場合、すべてのメッセージを結合した単一の instruct レスポンスを出力します。
  • それ以外の場合、allow レスポンス(stdout 空、終了コード 0)を出力します。

組み込みポリシー

src/hooks/builtin-policies.ts が 39 個の組み込みポリシーを BuiltinPolicyDefinition オブジェクトとして定義しています。
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
params を受け取るポリシーは、各パラメーターの型とデフォルト値を持つ PolicyParamsSchema を宣言します。ポリシー評価器は fn を呼び出す前に解決済みの値を ctx.params に注入します。デフォルト値は常に先に適用されるため、ポリシー関数は null チェックなしで ctx.params を読み取れます。 ポリシー内のパターンマッチングは生の文字列マッチングではなく、解析済みのコマンドトークン(argv)を使用します。これにより、シェル演算子インジェクションによるバイパスを防ぎます(例:sudo systemctl status * というパターンは、コマンドに ; rm -rf / を追加しても迂回できません)。

カスタムポリシー

src/hooks/custom-hooks-registry.tsglobalThis を使ったレジストリを実装しています。
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // テスト用
src/hooks/custom-hooks-loader.ts がユーザーのポリシーファイルを読み込みます。
  1. 設定から customPoliciesPath を読み取り、存在しない場合はスキップします。
  2. 絶対パスに解決してファイルの存在を確認します。
  3. すべての from "failproofai" インポートを実際の dist パスに書き換え、customPolicies が同じ globalThis レジストリに解決されるようにします。
  4. ESM 互換性を確保するため、推移的なローカルインポートを再帰的に書き換えます。
  5. 一時的な .mjs ファイルを書き出し、エントリファイルを import() します。
  6. getCustomHooks() を呼び出して登録済みフックを取得します。
  7. finally ブロックですべての一時ファイルを削除します。
エラーが発生した場合(ファイルが見つからない、構文エラー、インポート失敗など)、エラーは ~/.failproofai/hook.log に記録され、ローダーは空の配列を返します。組み込みポリシーへの影響はありません。 カスタムポリシーはすべての組み込みポリシーの後に評価されます。カスタムポリシーの deny はそれ以降のカスタムポリシーを短絡させますが(その時点ではすべての組み込みポリシーはすでに実行済みです)。

アクティビティのログ記録

各フックイベントの後、ハンドラーは JSONL の 1 行を ~/.failproofai/hook-activity.jsonl に追記します。
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
非 allow の判断をしたポリシーごとに 1 行記録されます。allow の判断はファイルサイズを抑えるため記録されません。

ダッシュボードのアーキテクチャ

ダッシュボードは App Router を使用した Next.js 16 アプリケーションで、React Server Components と Server Actions を採用しています。
app/
  layout.tsx                  ← ルートレイアウト(テーマ、テレメトリー、ナビゲーション)
  projects/page.tsx           ← サーバーコンポーネント:Claude プロジェクトの一覧
  project/[name]/page.tsx     ← サーバーコンポーネント:プロジェクト内のセッション一覧
  project/[name]/session/
    [sessionId]/page.tsx      ← サーバーコンポーネント:セッションビューアーの表示
  policies/page.tsx           ← クライアントコンポーネント:ポリシー管理 + アクティビティログ
  actions/
    get-hooks-config.ts       ← 設定とポリシー一覧の読み取り
    update-hooks-config.ts    ← ポリシーの有効/無効の切り替え
    update-policy-params.ts   ← ポリシーパラメーターの更新
    get-hook-activity.ts      ← アクティビティログのページネーション/検索
    install-hooks-web.ts      ← ブラウザからのフックのインストール/削除
  api/
    download/[project]/[session]/route.ts   ← CLI セッションごとのエクスポート(JSONL または JSON)
データフロー:
  • ページコンポーネントは lib/projects.tslib/log-entries.ts を呼び出して、ファイルシステムから直接プロジェクト/セッションデータを読み取ります(読み取りに API レイヤーなし)。
  • Policies ページはすべての変更操作(切り替え、パラメーター更新、インストール/削除)に Server Actions を使用します。
  • セッションビューアーは Claude の JSONL トランスクリプト形式をパースし、メッセージとツール呼び出しのタイムラインを描画します。
主要な設計方針:
  • データベースなし - すべての永続状態はプレーンファイル(~/.failproofai/~/.claude/projects/)に保存
  • 変更操作に Server Actions を使用 - CRUD 操作に REST API が不要
  • 読み取りページに React Server Components を使用 - 初期表示が高速でデータフェッチのクライアントバンドルが不要
  • インタラクティビティが必要な箇所にのみクライアントコンポーネントを使用(ポリシーの切り替え、アクティビティ検索、ログビューアー)

ファイル構成

failproofai/
├── bin/
│   └── failproofai.mjs           # CLI ルーター(フック / ダッシュボード / インストール など)
├── src/hooks/
│   ├── handler.ts                # フックイベントパイプライン
│   ├── builtin-policies.ts       # 39 個のポリシー定義
│   ├── policy-evaluator.ts       # ポリシー実行エンジン
│   ├── policy-registry.ts        # ポリシーの登録と参照
│   ├── policy-types.ts           # TypeScript インターフェース
│   ├── hooks-config.ts           # マルチスコープ設定の読み込み
│   ├── custom-hooks-registry.ts  # globalThis を使ったフックレジストリ
│   ├── custom-hooks-loader.ts    # ユーザー JS フック用の ESM ローダー
│   ├── manager.ts                # インストール / 削除 / 一覧操作
│   ├── install-prompt.ts         # インタラクティブなポリシー選択プロンプト
│   ├── hook-logger.ts            # hook.log へのログ記録
│   ├── hook-activity-store.ts    # hook-activity.jsonl へのアクティビティ永続化
│   └── llm-client.ts             # LLM API クライアント(AI 駆動ポリシー用)
├── app/                          # Next.js ダッシュボード(ページ + サーバーアクション)
├── lib/                          # 共有ユーティリティ
│   ├── projects.ts               # ファイルシステムから Claude プロジェクトを列挙
│   ├── log-entries.ts            # Claude トランスクリプト JSONL 形式のパース
│   ├── paths.ts                  # システムパスの解決
│   └── ...
├── components/                   # 共有 React UI コンポーネント
├── contexts/                     # React コンテキストプロバイダー(テーマ、自動更新、テレメトリー)
├── examples/                     # カスタムフックのサンプルファイル
└── __tests__/                    # ユニットテストと E2E テスト