.claude/settings.jsonが満たすべき PreToolUse hook wiring の正本。bin/plangate doctor --check-settingsがこの契約と実体を突合する。 適用はscripts/apply-claude-settings.sh(ユーザー実行。AI は self-mod ガードで.claude/settings.jsonを編集できないため)。
| Hook | command(必須トークン) |
|---|---|
| EH-1 plan-exists | scripts/hooks/check-plan-exists.sh |
| EH-2 c3-approval | scripts/hooks/check-c3-approval.sh |
| EH-3 plan-hash | scripts/hooks/check-plan-hash.sh ${PLANGATE_HOOK_TASK:-} ${PLANGATE_HOOK_FILE:-} |
| EH-6 forbidden-files | scripts/hooks/check-forbidden-files.sh |
| EH-9 delegation-commit-boundary | scripts/hooks/check-delegation-commit-boundary.sh |
${PLANGATE_HOOK_FILE:-} を第2引数に含む(P4(d) ファイルパス
感応 SKIP / TASK-0070 AC-8。これが本セッション通底の未適用 wiring)。.claude/settings.json の PreToolUse command 群に
すべて存在すれば doctor --check-settings PASS。1 つでも欠落で FAIL。""(省略)/ "*" は全ツール発火として契約充足に数える。
この解釈は scripts/check-settings-wiring.sh の has() と
scripts/apply-claude-settings.sh の包含判定で一致させること。
片方だけが * を全ツールとみなすと、適用側は「配線済み」・検証側は
「不足」となり 何度適用しても収束しない(#928 で実害化)。bin/plangate doctor --check-settings(未適用箇所を列挙し非0)sh scripts/apply-claude-settings.sh(冪等。ユーザーが実行)⚠️ 適用スクリプトの副作用(本契約の範囲外):
apply-claude-settings.shは本契約が定める PreToolUse 6 項目だけでなく、.claude/settings.example.jsonの 全 hook event(SessionStart / PostToolUse / Stop を含む)を取り込む。 そこには契約外かつ副作用の大きい hook が含まれうる(例: SessionStart のscripts/gh-pin-account.shはgh auth switchでマシン全体の gh CLI active account を切り替える)。また「不足を足すが削除はしない」方針の 裏返しとして、example から意図的に削除した hook は再実行のたびに復活 する(opt-out 手段は現状なし)。--all-eventsopt-in 化は #975 で follow-up。
.claude/settings.json の AI 直接編集は禁止(self-mod ガード・恒久制約)。
AI は契約定義・検証・適用 script 提供まで。適用は人間。| 層 | 検証対象 | 手段 | 役割 |
|---|---|---|---|
CI settings-drift(required) |
.claude/settings.example.json(契約 reference) |
check-settings-wiring.sh --target example |
正本 reference が契約と乖離しないことを保証(example が壊れたら全員に波及するため) |
bin/plangate doctor --check-settings |
.claude/settings.json(ユーザー実体) |
構造(JSON)検証 | 実環境の wiring 未適用=Shadow Config を検出 |
既存 bin/plangate doctor hook-wiring check |
.claude/settings.json vs .claude/settings.example.json |
既存 check(TASK-0069) | settings.example.json を契約整合させた結果、未適用を通常 doctor でも FAIL(従来の false-PASS を是正) |
.claude/settings.json は gitignore(ユーザーローカル)のため CI では検出
不可。実体 drift の検出は doctor(ローカル / V-1・handoff DoD)が担う。
両者は役割が異なり、どちらか一方では「Shadow Config を構造的に防ぐ」根拠に
ならない(CI=reference 健全性 / doctor=実体適用)。
V-1/handoff 完了の DoD(docs/workflows/05_verify_and_handoff.md
/ working-context.md)に
「doctor --check-settings PASS」を必須化。強制は次の二重で成立する:
doctor の hook-wiring FAIL: settings.example.json を
契約整合させたため、未適用環境では bin/plangate doctor(通常実行)も
FAIL する。doctor FAIL 状態での完了報告は Iron Law(検証証拠なしに完了
扱いしない)違反。doctor --check-settings: 構造検証で未適用箇所を決定論的に列挙。
既知の限界(V2 候補):完全な PreToolUse-hook レベルの機械 block—解消済 (PR #347)。❌ 未解消(#1078 実測 2026-08-13):.codex/hooks.json+.codex/hooks/eh-bridge.shで Codex CLI 側にも EH-1/2/3/6/9 が物理 PreToolUse block として配線済。.codex/hooks.jsonは parse 拒否され Codex 側の hook 登録は 0 件。EH-1/2/3/6/9 は Codex セッションで一度も発火していない。Claude Code 側は従来通り.claude/settings.jsonで配線。詳細は本ファイル後段の §Codex CLI parity 参照。
是正記録 2(2026-08-13 / #1078 / 本節で 2 度目の是正): 直下の「是正記録 1」で 「部分達成(5 / 11 wiring)・強制力は未検証」 へ書き換えたが、 これもまだ実態より甘かった。
codex app-serverの JSON-RPChooks/list(モデル呼び出しを 伴わない=課金ゼロ)で実測したところ、.codex/hooks.jsonは JSON 全体が parse 拒否されており、 PlanGate の hook は 1 件も登録されていない。すなわち EH-1 / EH-2 / EH-3 / EH-6 / EH-9 は Codex セッションで一度も発火していない(「未検証」ではなく 0 件で確定)。 「5 / 11」は 設定ファイルに記述されている件数であって、登録数でも強制力でもない。 3 軸を分けた現況は下表「parity の 3 軸」を参照。過去の記述は削除せず残す。本節では「達成済」も「部分達成」も強制力については使わない。 Codex 側の強制力は 0 / 11 であり、これは実測(
hooks/listのwarnings/ 登録 0 件)に基づく確定値である。
是正記録 1(2026-08-13 / #1078): 本節の見出しは 2026-05-25 の PR #347 以来 「達成済」 と記載していたが、 #1078 の実測で
.claude/settings.example.json側 11 wiring のうち Codex 側に あるのは 5 件(EH-1 / EH-2 / EH-3 / EH-6 / EH-9)で、6 件が欠落している ことが判明した。さらに 配線済み 5 件についても「実際に発火し block している」 実走証跡が無い(後述「未検証事項」)。過去の記述は削除せず、実測に基づく 現況を以下に追記する。「達成済」は EH-1/2/3/6/9 の 設定ファイル上の配線 に限った記述として読むこと。強制力の等価は本節時点では主張しない。model tier の parity: Claude Code は
.claude/agents/*.mdfrontmatter のmodel:(inherit/sonnet)、Codex は.codex/agents/*.tomlのmodel_reasoning_effort(low/medium)で同一の 2 tier を表現する。対応表の 正本はmodel-profiles.md§11。
「何件配線したか」と「何件効いているか」は別の数である。本節では以下 3 軸を分けて数える。 過去 2 回の誤りは、いずれも 軸 A の数を軸 C の主張に流用したことで起きた。
| 軸 | 定義 | 数え方 | Claude Code | Codex CLI |
|---|---|---|---|---|
| A. 記述(declared) | 設定ファイルに hook として書かれている件数 | .claude/settings.example.json / .codex/hooks.json を静的にパース |
11 | 5 |
| B. 登録(registered) | ランタイムが実際に読み込んで登録した件数 | Codex は hooks/list。Claude は同等の問い合わせ経路を本 PBI では使っていない |
未測定 | 0 |
| C. 強制力(enforced) | 実際に発火して block した件数(上限は軸 B) | 軸 B が上限を与える。下限は実走ログで確認(未取得) | 未測定 | 0(軸 B = 0 より上限 0) |
⚠️ Claude 側の B / C を「11」と読み替えないこと。 本 PBI は Claude 側について ランタイム登録状態を問い合わせていない(
.claude/settings.jsonは gitignore で リポジトリに存在せず、bin/plangate doctor --check-settingsは構造・契約の検証で あって「harness が hook を登録したか」の問い合わせではない)。 「設定の存在は動作の証拠ではない」という本節の教訓は Claude 側にも等しく効く。 Codex で起きたことが Claude で起きないと結論づける根拠は、本 PBI の範囲では無い。
.codex/hooks.json の top-level に仕様外キーが 2 つある:
| 行 | キー | 扱い |
|---|---|---|
| 2 | $schema_note |
仕様外(JSON にコメント構文が無いため注記として置かれたもの) |
| 3 | $note |
仕様外(同上) |
Codex CLI の hooks config パーサは top-level に description と hooks の 2 キーしか許容しない
(deny_unknown_fields)。1 キーの違反でファイル全体が捨てられる(部分適用ではない)。
hooks/list(cwd = 本リポジトリ)の実応答 warning(verbatim):
failed to parse hooks config <repo>/.codex/hooks.json: unknown field `$schema_note`, expected `description` or `hooks` at line 2 column 16
このとき登録されたのは river-review plugin の PostToolUse 1 件のみで、
PlanGate の PreToolUse 5 件は 0 件。サンドボックスで双方向に再現済み
(注記キーを足すと hooks[] が空・外すと登録される=決定論的)。
なお project trust は本件の原因ではない(本リポジトリは trusted 済み)。原因は parse 拒否である。
この 2 行を消すだけの PR を作ってはならない。 「top-level の
$schema_note/$noteを消せば直る」は 誤りである。2 行を消すと
hooks/list上は PlanGate hook 5 件が登録され、trustStatusも付く。 外形上は「parity 回復」に見える。しかしeh-bridge.shの欠陥により hook は 空入力で PASS し続け、強制力は 0 のままである。これは「Codex が使えなくなる」より危険である。 本節が潰そうとしている 「登録 ≠ 強制力」という誤りを、是正行為そのものが再生産する経路になる。 「登録 5 件・warnings 空」という緑のシグナルだけが増え、実態は 0 のまま据え置かれる。
除去は
eh-bridge.shの I/O 契約修正と同一 PR でなければならない。 順序は stdin 転送の実装を先行必須とし、実際に block される証跡が取れて初めて 注記キーを除去する。受入基準に「hooks/listに登録された」を使ってはならない (登録は前提条件であって成果ではない)。受入基準は deny が実際に返ることに置く。
当初この節には「除去すると全操作が deny され Codex が使用不能になる」と書いていたが、
実測で否定された。eh-bridge.sh に PreToolUse payload を直接投入した結果:
| 対象ファイル | hook | rc | decision |
|---|---|---|---|
bin/plangate |
check-plan-exists / c3-approval / forbidden-files / plan-hash | 0 | allow |
docs/working/TASK-1078/plan.md |
同上 4 本 | 0 | allow |
.claude/settings.json(HO パス) |
同上 4 本 | 0 | allow |
12 / 12 が allow。Hardening Override パスですら通る。
理由は 2 つで、いずれも「deny 側」ではなく「allow 側」に倒れる:
eh-bridge.sh L33 で INPUT=$(cat) により吸い切り、L69 の
hook 起動へ渡していない)。hook は PLANGATE_HOOK_FILE / PLANGATE_HOOK_TASK の
env のみという縮退した文脈で走り、判定材料が無いため rc=0(PASS)を返す*) 分岐)。deny になるのは
rc が 1 または 2 のときだけ「パス解決の not-found → deny」は配線済み 5 本には効かない。 配線されている 5 本は実体が
scripts/hooks/に実在するため not-found 分岐に入らない。 not-found deny が発火するのは 未配線の hook(EH-12 / EH-13 — 実体がscripts/直下)を名前だけ足したときであり、その場合もそもそも現状は呼ばれない。
eh-bridge.sh の構造欠陥 一覧(本表を唯一の正本とする)他ファイルはこの表を参照すること(個数・内容を各所で書き下すと不一致が生じる)。
| # | 欠陥 | 位置 | 倒れる向き |
|---|---|---|---|
| B-1 | stdin を吸い切り hook へ渡さない(Codex の入力経路は stdin のみ) | L33 / L69 | allow(判定材料が無く rc=0) |
| B-2 | 未知 exit code を allow に落とす(deny は rc 1 / 2 のみ) | L86-89 | allow(fail-open) |
| B-3 | hook パスを scripts/hooks/ 固定で解決 → not-found で deny |
L26 / L28-29 | deny(ただし未配線 hook を足したときのみ) |
| B-4 | matcher に Codex に存在しないツール名(Edit / Write)を含む |
.codex/hooks.json |
死に文字列(実質 apply_patch のみ一致) |
現況で支配的なのは B-1 / B-2= allow 側。B-3 は将来 EH-12 / EH-13 を配線する ときに初めて効く。「bridge は deny 側に厳しい」という読みは誤りである。
本件の本質は 配線の記述を動作の証拠として扱っていたことにある。
.codex/hooks.json が存在し・中身が意図どおりに書かれていたため、
レビューでも doctor でも「配線済み」と判定され続けた。codex doctor は hook を一切報告しない(config / auth / sandbox / mcp のみ)。
したがって doctor の PASS は hook が登録されていることの根拠にならない。一般則: 「設定ファイルが存在する / 正しく書けている」は ランタイムがそれを受理した ことを意味しない。強制力を主張するには ランタイム側の登録状態を問い合わせた証跡が要る。 これは Shadow Config(本ファイル後段「Wiring Integrity Enforcement(#500)」)の Codex 版であり、同じ検出原理(実体への問い合わせ)で塞ぐ。
hooks/list による機械検出課金ゼロで登録状態を機械検出できるため、これを doctor / CI に組み込むことを 必須スライスとする。
codex app-server(stdio JSON-RPC)の hooks/list メソッド。モデル呼び出しを伴わない。hooks[] { key, eventName, matcher, command, source, currentHash, trustStatus, enabled }
+ warnings[] / errors[]。trustStatus の enum は managed / untrusted / trusted / modified。warnings[] が空(parse 拒否・trust 警告を見逃さない)enabled が truetrusted_hash の運用も併せて必要: project hooks は既定 untrusted で登録され、
hooks.json を編集するたび hash が変わる。「編集 → 再 trust」が運用フローに要る
(hash は hook 単位。同一ファイル内の別 matcher group を個別に trust できる)。git grep による全数照合の結果。本 PR で是正した箇所と、未是正のまま残る箇所を
明示的に区別する(未是正箇所を書き残さないと網羅性が担保できないため)。
| ファイル | 箇所 |
|---|---|
docs/ai/settings-wiring-contract.md |
本節見出し / L79 既知の限界 / 三層表 / 等価強制マトリクス前文 / bridge 動作 / U-3 |
docs/plangate.md |
§Codex CLI parity 見出し・本文・強制マトリクス / v8.10 年表行 |
docs/ai/harness-improvement-roadmap.md |
v8.10.0 Codex CLI parity 行 / 完了確認の注記 |
docs/ai/hook-enforcement.md |
物理配線 11/12 の注記・配線状態表の Codex 列 |
.agents/skills/ai-dev-exec/SKILL.md |
「物理 hook 等価達成」ブロック |
.agents/skills/local-exec-handoff/SKILL.md |
「EH-1/2/3/6/9 が自動発火する」 |
plugin/plangate/skills/{ai-dev-exec,local-exec-handoff}/SKILL.md |
上記の同期先(scripts/sync-plugin-plangate.sh により生成) |
| ファイル:行 | 残存主張 | 理由 |
|---|---|---|
CLAUDE.md:34 |
「EH-1/2/3/6/9 を Codex session 中 … 物理発火」 | HO パス。patch を docs/working/TASK-1078/patches/CLAUDE.md.codex-parity.patch に用意(適用は Human) |
AGENTS.md:18 / AGENTS.md:48 |
「物理 hook bridge を有効化」「Codex 側でも発火」 | HO パス。patch を docs/working/TASK-1078/patches/AGENTS.md.codex-parity.patch に用意(適用は Human)。Codex セッションが読む側の正本のため実害が最大 |
README.md:450 |
「完全対応(物理 hook parity 達成済)」 | 本 PR スコープ外(別 PBI で是正) |
README.md:90 |
「物理配線は 11/12」 | 同上。hook-enforcement.md と同じ数え方に依存 |
docs/ai-driven-development.md:312 |
「等価な物理 hook 強制が有効化されている (#347)」 | 同上 |
.codex/README.md:43 |
「.codex/hooks 物理 hook で強制」 | .codex/ は本 PBI で変更しない(bridge 修正 PBI で扱う) |
.codex/hooks.json:3 |
$note 内の「等価な強制力を Codex セッションで実現する」 |
同上(この行の除去自体が上記「単独除去は禁止」の対象) |
.codex/skills/{ai-dev-exec,local-exec-handoff}/SKILL.md |
.agents/skills と同文 |
.codex/ は変更しない。scripts/install-plangate-skills-to-codex.sh による同期で追従させる |
tests/extras/README.md:14 |
「Codex CLI 物理 hook parity 検証 (#347)」 | テスト説明。別 PBI |
CHANGELOG.md v8.10.0 節 |
「Codex CLI parity 完成(100% 達成)」 | 履歴のため不改変(当時の記録)。誤読防止の注記追加は別 PBI |
Human 適用待ちの patch:
docs/working/TASK-1078/patches/配下。 HO パスのため AI は適用できない(CLAUDE.md/AGENTS.md)。 適用コマンド:git apply docs/working/TASK-1078/patches/<name>.patch追跡はdocs/working/TASK-1078/status.mdの BLOCKED 項目。 本節の実測根拠:docs/working/TASK-1078/evidence/hooks-list-reverify.md
判明事項 (2026-05-25 PR #347): OpenAI Codex CLI は PreToolUse / PostToolUse hook API を公式提供しており、Claude Code の hook 仕様と直接互換 (matcher / stdin JSON / exit 2 で deny / hookSpecificOutput.permissionDecision)。公式仕様: https://developers.openai.com/codex/hooks
| 層 | 機構 | カバー範囲 |
|---|---|---|
| 1. Session 前 | scripts/codex-guarded.sh (PR #343) |
validate / doctor / EH-8 privacy / plan.md hash snapshot |
| 2. Session 中 (物理 pre-Write block) | .codex/hooks.json + .codex/hooks/eh-bridge.sh (PR #347) |
|
| 3. Session 後 | scripts/codex-guarded.sh post-flight |
plan.md hash drift 検知 + validate 再実行 |
層 2 の但し書き(#1078 / 2 度目の是正で強化): 当初「Codex 側でも発火」と書き、 1 度目の是正で「配線されていることを指す(実走証跡は無い)」に緩めたが、 実測では層 2 はまったく機能していない。
.codex/hooks.jsonは parse 拒否され hook が 1 件も登録されていない(上記「設定ファイル全体が parse 拒否されている」)。 層 2 は現時点で存在しないものとして扱うこと。層 1 / 層 3 の但し書き:
scripts/codex-guarded.shの pre/post-flight は本件と 独立に機能するが、それは正規入口(scripts/codex-guarded.sh)を経由した場合に 限る。正規入口の使用を強制する機械ゲートは存在しない(.github/workflows/配下にcodex-guardedの参照は 0 件=規範層の運用に依存)。素のcodex/codex execを直接起動した場合、層 1 / 層 2 / 層 3 のいずれも働かない。
比較対象は .claude/settings.example.json(.claude/settings.json は
gitignore でリポジトリに存在しないため)と .codex/hooks.json の全数差分。
⚠️ 本表は「軸 A(記述)」の表である。 12 wiring 中 Codex 側に記述があるのは 5 件・ 欠落 7 件(#1267 が
settings.example.jsonへ EH-3b =Bashmatcher のcheck-plan-hash.shを追加したため、#1078 時点の 11 → 12。実測:settings.example.jsonの hook コマンド総数 12)。ただし記述のある 5 件も含め、Codex 側の登録数は 0・強制力は 0 (上記「parity の 3 軸」)。本表の ✅ は「効いている」ではなく「書かれている」を意味する。
| # | 強制 | event / matcher | Claude Code (settings.example.json) |
Codex CLI (.codex/hooks.json) |
|---|---|---|---|---|
| 1 | EH-1 plan-exists | PreToolUse Edit\|Write |
✅ | ✅ (matcher apply_patch\|Edit\|Write) |
| 2 | EH-2 c3-approval | PreToolUse Edit\|Write |
✅ | ✅ 同上 |
| 3 | EH-3 plan_hash | PreToolUse Edit\|Write |
✅(引数 ${PLANGATE_HOOK_TASK:-} ${PLANGATE_HOOK_FILE:-} を渡す) |
⚠️ 配線あり・引数を渡さず env のみ(引数 / env / stdin の 3 系統が hook ごとに不統一) |
| 4 | EH-6 forbidden_files | PreToolUse Edit\|Write |
✅ | ✅ 同上 |
| 5 | EH-9 delegation-commit-boundary | PreToolUse Bash |
✅ | ✅ |
| 6 | EH-13 approval-token-write(Edit/Write 系) | PreToolUse Edit\|Write |
✅ scripts/check-approval-token-write.sh |
❌ 未配線 |
| 7 | EH-13 approval-token-write(Bash 系) | PreToolUse Bash |
✅ 同上 | ❌ 未配線 |
| 8 | EH-12 git-destructive guard | PreToolUse Bash |
✅ scripts/check-git-destructive.sh |
❌ 未配線 |
| 9 | gh-pin-account | SessionStart | ✅ scripts/gh-pin-account.sh |
❌ 未配線(Codex 側に SessionStart 配線が無い) |
| 10 | check-post-edit-diff | PostToolUse Edit\|Write\|MultiEdit |
✅ scripts/hooks/check-post-edit-diff.sh |
❌ 未配線(Codex 側に PostToolUse 配線が無い) |
| 11 | check-stop-diff-status | Stop | ✅ scripts/hooks/check-stop-diff-status.sh |
❌ 未配線(Codex 側に Stop 配線が無い) |
| 12 | EH-3b plan_hash(Bash レーン / #1104) | PreToolUse Bash |
⚠️ 配線あり・現状 no-op(下記注) | ❌ 未配線 |
#12(EH-3b)の実測(2026-08-28 /
origin/main=3f0cadd):check-plan-hash.shの対象パス抽出はtool_input.file_pathのみを見る。Bash の PreToolUse payload が持つのはtool_input.commandなのでtarget_fileは常に空になり、HO 判定も write-intent 判定も行われない。配線されただけの状態では (a) no-task セッションで全 Bash がexit 2、(b)PLANGATE_SKIP_REASONで回避するとskip-decision-log.jsonlへ未追認エントリが積まれcheck-skip-acknowledged.shが FAIL、という 摩擦だけが残る。是正 patch と残存脅威モデル:docs/working/_reports/1104-bash-lane-noop-patch-applicable.md。 回帰テスト:tests/extras/ta-79-eh3-bash-lane.sh。#1104 は open(Bash コマンド文字列からの 書き込み先抽出は未実装)。
欠落 6 件を「単に名前を足せば直る」と読んではならない。#6〜#9 の hook 実体は
scripts/ 直下にあり、eh-bridge.sh は scripts/hooks/<NAME> をハードコード
で解決するため、名前だけ足すと not-found 分岐で無条件 deny になる(実測)。
是正には bridge の I/O 契約変更(パス解決 / stdin 転送 / payload 正規化)が要り、
実装は本節の範囲外(#1078 の別スライス)。
.codex/hooks.json の matcher は apply_patch|Edit|Write だが、Codex CLI が送る
tool_name は apply_patch / Bash のみで、Edit / Write / MultiEdit は
Codex に存在しない(0.144.1 バイナリ埋め込みの JSON Schema と
公式仕様 の一致で確認)。
したがって matcher 中の Edit / Write は Codex 側では一致し得ない死に文字列であり、
実質 apply_patch のみが一致する。また apply_patch は file_path を持たない
ため、bridge は *** Update/Add/Delete File: を正規表現で抽出している。
以下のうち U-3 は #1078 の hooks/list 実測で決着した(ただし 想定と別の原因で。
下表参照)。U-1 / U-2 は引き続き未検証であり、現時点では「Codex 側の強制力が
働いている」ことの根拠にならない。文言・状態表は実走証跡が得られるまで
「未検証」のまま扱う。
U-1 / U-2 のステータスは「観測あり・因果未確定」(「未観測」ではない)。 別走で bare
allowは無害(弾かれず実害が出ていない)、空 reason のdenyは fail-open が再現との観測が得られているが、permission_mode=bypassPermissionsの 交絡があり、観測された挙動が hook 応答の解釈によるものか permission_mode による ものか切り分けられていない。したがって 因果は未確定であり、確定するまで 本節に結論を書き込んではならない。なお U-1 / U-2 がどちらに転んでも軸 C(強制力 0 / 11)は変わらない — 登録 0 件が上位の制約だからである。
| ID | 未検証事項 | 分かっていること(実測) | 分かっていないこと |
|---|---|---|---|
| U-1 | bridge の allow 応答が受理されるか |
CLI バイナリに PreToolUse hook returned unsupported permissionDecision:allow という文字列が実在する。eh-bridge.sh は正常系で bare permissionDecision: "allow" を返している |
実行時に実際に allow が unsupported として弾かれるか。弾かれた場合の Codex 側の既定挙動(allow 継続 / エラー停止) |
| U-2 | reason 空文字の deny が deny として通るか | 同じく deny without a non-empty permissionDecisionReason という文字列が実在する。PlanGate hook が無出力で終了した場合、bridge の reason は空文字になりうる |
空 reason の deny が無視される(= fail-open)か否か |
hooks/list 実測で決着。発火していない。ただし原因は trust ではなく .codex/hooks.json の parse 拒否(上記「設定ファイル全体が parse 拒否されている」)。EH-1/2/3/6/9 は登録 0 件=一度も発火していない |
— (「trust が原因」という当初の仮説は否定された。trust は本件の原因ではないが、parse 拒否を直した後には別途 trusted_hash 運用が必要になる) |
U-1 / U-2 の実走はモデル API 呼び出しを伴うため、実施可否は Human 判断(#1078)。 一方 登録状態の確認は
hooks/listで課金ゼロに行える(上記「後続の必須スライス」)。 Codex セッションの安全性を.codex/hooks.jsonの存在に依拠して評価しないこと — 存在していても 現に登録されていないというのが本件の実測結果である。 Session 前後のscripts/codex-guarded.sh(層 1 / 層 3)は本件と独立に機能する。
等価強制マトリクスは EH-1/2/3/6/9 の 5 行のみで、行単位では正しいが
集合として不完全だった。新しい hook(EH-12 / EH-13 / SessionStart /
PostToolUse / Stop)を .claude/settings*.json に追加した際に
本マトリクスへ追記することを求める運用ルールも、両者の集合差を検出する
機械チェックも存在しない。結果、追加のたびに parity のギャップが静かに広がり、
見出しの「達成済」だけが残った。機械検出の追加は #1078 の後続スライス候補。
さらに深い原因(2 度目の是正で判明): 上の説明は 軸 A(記述)の集合差しか 見ていない。軸 A を全数化しても、記述された 5 件が実は 0 件しか登録されていない ことは検出できなかった。集合差の機械チェックを足すだけでは不十分であり、 ランタイムの登録状態(
hooks/list)を問い合わせる検査が要る。 「マトリクスを全数化する」ことと「強制力を確認する」ことは別作業である。
⚠️ 以下 1〜6 は PR #347 時点の設計意図であり、現況の記述ではない。 手順 1 の時点で
.codex/hooks.jsonが parse 拒否されるため、2 以降は 1 度も実行されていない。
apply_patch / Edit / Write / Bash 呼び出し前に .codex/hooks.json を参照command (= .codex/hooks/eh-bridge.sh <HOOK_NAME>) が起動*** Update/Add/Delete File:) を抽出PLANGATE_HOOK_FILE / PLANGATE_HOOK_TASK を設定し scripts/hooks/<HOOK_NAME> を起動hookSpecificOutput.permissionDecision (allow / deny) に翻訳手順 3〜4 の実測補足(#1078): bridge は stdin を
INPUT=$(cat)で吸い切り、 手順 4 の hook 起動時にその stdin を hook へ渡していない(渡すのはPLANGATE_HOOK_FILE/PLANGATE_HOOK_TASKの env のみ)。Codex の hook 入力経路は stdin のみ(CODEX_HOOK_*系 env はバイナリ内に 0 件)であるため、 stdin で判定する hook(例: v8.19.0 で stdin 常時独立評価・fail-closed 化された EH-13)は この bridge 経由では正しく判定できない。手順 6 の「物理 block」も 上記「未検証事項」U-1〜U-3 の成立が前提であり、実走証跡は未取得。
.claude/settings.json / bin/plangate / scripts/hooks/*.sh 等の Hardening Override 領域を改変するのは依然として AI 改変不可 (Human-owned).codex/hooks.json / .codex/hooks/*.sh) は AI-owned (Override 対象外)Status: Specification(方針確定。実装は HO パス絡みのため受け入れ条件ごとに段階 PBI へ分解) 出自: river 3 相レビュー(2026-06-08、Gemini + コードベース調査で代替実行 / external-reviewer-interface §10 unavailable 準拠)で検出した「規範↔実装」乖離 (Shadow Config)への是正方針。
settings.example.json は配布テンプレートのため warning モード(PLANGATE_HOOK_STRICT
未設定)を既定とし、導入直後の破壊を避ける。PLANGATE_HOOK_STRICT=1 を
設定し EH-2 / EH-3 / EH-6 等を block モードで動かす。bin/plangate doctor --check-settings は、コード整合性に加えて 現在のモードで必須
フックが strict 配線・有効化されているか を検証する。採番改訂(TASK-1023 G-6 / Human 裁定 2026-08-10): 本節は当初 EH-10 として 採番していたが、
hook-enforcement.md側で EH-10 / EH-11 は #760 / #762 用に予約済み、EH-12 は protected branch 破壊的 git 操作ブロック (check-git-destructive.sh)に採番済みであり衝突していた(R-033)。予約体系を 尊重し、衝突しない最小の空き番号 EH-13 へ改番する(G-6=(b))。
scripts/check-approval-token-write.sh(c3.json / maintenance.json 等の承認トークンへの
AI 書込みガード)を EH-13 として正規採番し、PreToolUse(Edit|Write) と
PreToolUse(Bash) に配線する(Bash matcher は TASK-0128 R-002 / 実配線済み。
MultiEdit は現行 Claude Code 2.1.226 に tool 自体が存在せず到達経路がない —
TASK-1023 到達性実測 / G-9=(i))。PLANGATE_HOOK_FILE を自動 export しないため、.claude/settings.json の配線で ${PLANGATE_HOOK_FILE:-} を 引数として明示的に渡す(EH-3 と同様)。check-approval-token-write.sh は引数 $1 をターゲットファイルパスの fallback として受け取れるよう実装する(env のみ参照だと Claude Code 環境下でガードがスルーされる)。| 受け入れ条件 | 実装方針 | 主パス(HO) |
|---|---|---|
| EH-2(c3_status)strict 化 | EH-3 と同じ python3 strict JSON 解析へ置換(permissive な grep/sed 抽出を廃止し EH-3 と対称化) | scripts/hooks/check-c3-approval.sh |
| EH-1/EH-2 stdin fallback | env 未注入時に stdin tool_input.file_path から解決し SKIP(allow) を解消 |
scripts/hooks/*.sh |
| maintenance verdict テスト | VALID / CONSUMED / OUT_OF_SCOPE / HARDENING_OVERRIDE の fixture + assert を追加 | tests/hooks/ |
| ta-06 ログ握りつぶし解消 | >/dev/null 2>&1 圧縮をやめ、どの EH が落ちたかをログに残す |
tests/.../ta-06-hooks.sh |
本仕様の HO 実装は以下の順で段階 PBI に分解する(各々 plan → C-3 承認 👤 → exec、 HO 適用は Human):
scripts/check-settings-wiring.sh の checks リストにも EH-13(check-approval-token-write.sh)を追加し、CI / ローカルの契約ドリフト検知(--target example)に組み込んで配線漏れを防ぐ各段階は承認境界(HO)の変更を含むため mode-classification.md により最低 high・
Standard C-3 同期固定(autonomous APPROVE 無効)とする。
Status: Implemented(
scripts/apply-task-0143-eh457-wiring.sh --apply適用後に有効)
PreToolUse hook ではなく bin/plangate CLI サブコマンド経由で発火する配線。
| Hook | CLI 配線先 | 発火タイミング |
|---|---|---|
EH-4 (check-test-cases.sh) |
bin/plangate verify <TASK> |
V-1 実行前(strict=1、test-cases.md なしで block) |
EH-5 (check-verification-evidence.sh) |
bin/plangate verify <TASK> |
V-1 通過後(warn のみ、evidence なしで WARNING) |
EH-7 (check-merge-approvals.sh) |
手動呼び出し推奨 | merge 前: sh scripts/hooks/check-merge-approvals.sh <TASK> |
bin/plangate doctor の === CLI Hook Wiring (EH-4/5/7) === セクションが:
bin/plangate verify への配線状態(grep 確認)を PASS/WARN で報告# 差分確認(必須)
sh scripts/apply-task-0143-eh457-wiring.sh --dry-run
# 適用
sh scripts/apply-task-0143-eh457-wiring.sh --apply
適用後: bin/plangate doctor の出力で [PASS] EH-4 wired / [PASS] EH-5 wired を確認する。
Status: Implemented(
scripts/apply-task-0144-c3-mode.sh --apply適用後に有効)
.plangate.yml プロジェクト設定で C-3 承認モードを選択できる経路。
| モード | 動作 | c3.json の source フィールド |
|---|---|---|
cli(デフォルト) |
bin/plangate approve <TASK> で対話的に承認・c3.json 生成 |
"cli" |
conversation |
会話内で人間が APPROVE 発話 → AI が exec 前に c3.json を生成 | "conversation" |
approvals/c3.json + conversation mode → SKIP (exit 0)。c3.json の中身検証は EH-2 と AI 生成コードに委ねるscripts/hooks/check-plan-hash.sh に追加された判定:
target_file が docs/working/TASK-*/approvals/c3.json にマッチ.plangate.yml を読んで c3_approval.mode を取得conversation の場合 → EH-3_C3_CONVERSATION_SKIP をログに記録して exit 0(Write を許可)cli の場合 → 既存の maintenance / SKIP_REASON 判定に進む| ファイル | 種別 | 説明 |
|---|---|---|
.plangate.yml |
設定ファイル | プロジェクト設定(c3_approval.mode: cli\|conversation) |
schemas/plangate-config.schema.json |
Schema(HO) | .plangate.yml の JSON Schema 検証定義 |
schemas/c3-approval.schema.json |
Schema(HO 変更) | source フィールドを追加(optional) |
scripts/hooks/check-plan-hash.sh |
Hook(HO 変更) | conversation SKIP 経路を追加 |
bin/plangate |
CLI(HO 変更) | _read_plangate_config() 追加 / source: "cli" / doctor セクション追加 |
# 差分確認(必須)
sh scripts/apply-task-0144-c3-mode.sh
# 適用
sh scripts/apply-task-0144-c3-mode.sh --apply
適用後: bin/plangate doctor の出力で === C-3 Approval Mode === セクションを確認する。