Skip to content

2026-07-07 学習ログ

やったこと(誤爆修正: mktemp管理下の一時ディレクトリ後始末をBLOCKしていた問題 / issue #82)

Section titled “やったこと(誤爆修正: mktemp管理下の一時ディレクトリ後始末をBLOCKしていた問題 / issue #82)”
  • #65のマージ検証中、.claude/settings.json のPreToolUseフックがファイルウォッチャー経由で即座にこのセッションに反映され、.ai-harness/tests/guard-corpus/run_guard_tests.sh 自身の実行が誤爆でBLOCKされる事態に遭遇(harnessが自分自身のテストスイートをBLOCKするという自己言及的な発見)。
  • 原因1: 二段迂回対策・--fileモードのファイル内容スキャンが、対象の削除系コマンドをそのまま判定関数に渡しており、mktemp -d で生成した一時ディレクトリの後始末という定型的に安全なイディオムでも、引数がクォートされた変数かどうかを考慮せず削除フラグの有無だけでBLOCKしていた。トップレベルのコマンド判定パス(クォート内リテラル除去)も、フラグ自体はクォートの外にあるため本質的に無力だった。
  • 対応: 変数が同一コンテンツ内で mktemp 経由で生成されたことが静的に確認できる場合に限り、その変数への削除呼び出し全体(フラグ+完全にクォートされた変数参照)を安全な定型パターンとして判定除外する redact_mktemp_cleanup を新設。未クォート形式は対象外(クォート境界を安全確認の拠り所にするため)。
  • 原因2(修正の過程で新たに発覚): ファイル内容の判定に「クォート除去後もなお bash -c/eval/xargs が残っていれば生内容も再スキャンする」というトップレベルと同じ二段構えを素朴に適用したところ、複数行ファイルのどこか1行に正当な bash -c 呼び出しがあるだけでファイル全体が生テキストで再スキャンされ、他の行にあるクォート内の危険文字列(テストフィクスチャ等)まで拾ってしまう新たな誤爆を生んだ。単一コマンド文字列では自然だった設計が、複数行ファイルには単純に転用できないことが判明。
  • 対応2: scan_content_by_line を新設し、mktemp変数の検出はファイル全体に対して1回(代入と削除呼び出しは別の行にまたがるのが通常のため)、クォート除去・生内容再スキャンの判断は行単位で行うよう分離。
  • run_guard_tests.sh に回帰ケースを8件追加(mktemp管理下の一時ディレクトリ後始末、クォート内の危険文字列を含むテストフィクスチャ生成スクリプト、双方について未追跡/追跡済みクリーンの両状態、および --file モード分)。既存99件+新規8件=107件全件パス。
  • run_guard_tests.sh 自体を丸ごと実行しようとすると、上記修正後もなお別の理由でBLOCKされることを確認した: このスクリプトのoverride検知テスト部分は、ガードの動作を検証するために本物の危険文字列rm -rf /tmp/... 等)を意図的にテストデータとして埋め込んでおり、これは誤爆ではなく「危険パターン検出器のテストスイート自身が危険パターンを含む」という構造的な自己参照の限界。ユーザーの判断により、この検証作業に限り HARNESS_ALLOW=1 の静的トークンをコマンド文字列の先頭に置く形(実際の子プロセスにはoverride環境変数を伝播させない)で回避し、検証を完了した。この自己参照問題自体の恒久対応は本issueのスコープ外とし、深追いしなかった。
  • .claude/settings.json のPreToolUseフックは、issue #65 で判明した通りファイルウォッチャーで即座に反映されるため、harnessの設定変更を検証している最中のセッション自体が、たった今マージした変更の影響を受ける(自分で自分の首を絞めうる)。harness自身の改修作業は、常にこの自己適用リスクを意識する必要がある。
  • 「危険パターンを検出するテストスイートは、その性質上、危険パターン検出器に引っかかりやすい」という自己言及的な緊張関係がある。この緊張は完全には解消できず、mktemp後始末のような「一般的で安全なイディオムの誤爆」と、「テストフィクスチャが意図的に含む本物の危険文字列」は、性質の異なる問題として区別して扱う必要があった。
  • HARNESS_ALLOW=1 <command> は静的な文字列トークンとしてガードのoverride検知にマッチするだけで、実際にシェルの環境変数として子プロセスに伝播するかどうかとは独立している。HARNESS_ALLOW=1 true; <実際に実行したいコマンド> のように先頭の一行だけをoverride検知用のダミーにすることで、ガードの静的判定(override扱い=許可)と、実際に実行されるコマンドの環境(override環境変数なし=本来のロジックで動作)を分離できる。

やったこと(Codex 機構配線・pre 系フック / Track C-5・issue #67)

Section titled “やったこと(Codex 機構配線・pre 系フック / Track C-5・issue #67)”
  • 着手前に「状態」(.codex/config.toml[[hooks.Stop]] のみ)を実ファイルで確認、ずれなし。#75/#78 のガード(check_dangerous_command.sh/check_sensitive_path.sh/guard_lib.sh)も想定通り改修済みであることを確認して着手。
  • 公式仕様(https://developers.openai.com/codex/hooks)を WebFetch 3回 + curl で取得した生 HTML から逐語引用して裏取り(WebFetch 単体の要約は “allow” の扱いなど回答間で微妙にブレたため、生 HTML の該当行を直接 grep/sed で確認して確定させた)。
    • PreToolUse は Bash・apply_patch(ファイル編集)・MCP ツール呼出しの実行前に発火。matchertool_name に適用(apply_patch/Edit/Write のいずれで書いても、実際に飛んでくる tool_input.tool_name は常に "apply_patch" に正規化される)。
    • 入力は tool_input.command(Bash はシェル文字列、apply_patch は V4A 形式パッチ本文)。
    • 出力は {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}} で遮断。
    • ask 相当は存在しない: 公式ドキュメントに逐語で "permissionDecision: \"ask\", legacy decision: \"approve\", continue: false, stopReason, and suppressOutput are parsed but not supported yet. Codex marks the hook run as failed, reports the error, and continues the tool call." と明記されている。つまり実装できる能動判断は "deny" のみで、"ask" は事実上フェイルオープン(ツール呼出しは継続される)。
    • 既知の遮断不能領域も明記あり: "This doesn't intercept all shell calls yet, only the simple ones... doesn't intercept WebSearch or other non-shell, non-MCP tool calls."(unified_exec 経由の一部シェル・WebSearch 等)。
  • .ai-harness/scripts/hooks/codex-pretool.sh を新設。tool_nameBash/apply_patch のみに絞り、tool_input.commandcheck_dangerous_command.sh にそのまま通す(apply_patch のパッチ本文もトークン分割経由で機密パス判定が効くため、専用のファイルパス抽出ロジックは不要と判断)。degrade 方針:
    • BLOCK(exit 2)→ 実際に permissionDecision:"deny" で遮断。
    • HARNESS_ALLOW override(BLOCK→OVERRIDE, exit 0)→ deny しない。ログは guard_lib.sh 側で完結、Codex 側にユーザー確認を挟む手段はない(機構的 degrade。policy.md §9 の文章契約でカバー)。
    • WARN(exit 3。Claude Code なら ask 相当)→ ask が無いため deny にはせず素通し(過遮断回避)。ログのみ記録し、policy.md §4/§6/§7 の文章契約でカバーする degrade とする。
    • 想定外の終了コード → フェイルクローズで deny。
  • .codex/config.toml[[hooks.PreToolUse]]matcher = "Bash|apply_patch")を追加配線(Orca のグローバル hook スロットには一切触れず、リポジトリ層のみ)。
  • スモークテスト:
    • run_guard_tests.sh 実行 → 99件中0件失敗。
    • codex-pretool.sh を実際の配線コマンドとして、公式スキーマ通りの stdin JSON で直接7パターン検証(Bash BLOCK/pass、WARN 素通し、apply_patch BLOCK(.env 更新)/pass、MCP対象外は素通し、HARNESS_ALLOW override 素通し)→ 全て設計通り。
    • codex exec(read-only sandbox)で rm -rf /tmp/definitely-not-a-real-dir-xyz123 を実行させたところ exec_command failed ... rejected: blocked by policy で拒否されたが、guard.log に対応する新規エントリが無く、拒否メッセージにも本アダプタ特有の日本語 BLOCK 文言が含まれなかったため、この特定の拒否は Codex 自身の read-only サンドボックスによるものであり、本 PreToolUse フックが実際に発火した証拠としては採用しないと判断した。disambiguation のため danger-full-access での再試験を試みたが、Claude Code 側の auto mode classifier に「未承認の危険な agent 起動」として2回ブロックされた(正しい安全動作のため深追いせず中止)。実フック経由の確認は上記の直接スクリプト起動(7パターン)を一次証跡として扱う。
  • Codex の Hooks 公式ページは、初回の WebFetch 要約では「ask/allow どちらも parsed-but-not-supported」と回答したが、2回目の要約では「allow は supported」と回答がブレた。生 HTML を curl で取得し該当パラグラフを grep -nsed -n で直接読むことで確定させた(allowupdatedInput によるリライト込みで supported、ask のみ non-functional)。要約ベースの WebFetch は決定的事実の確認には不十分で、生ソースへのフォールバックが必要になる場面がある。
  • codex exec を使った真の end-to-end 検証は、(1) sandbox モードそのものが OS レベルで書込みを拒否しうるため「拒否された」だけでは自作フックの発火証拠にならない(guard.log の有無や拒否メッセージの文言で照合する必要がある)、(2) 再試行のための sandbox 緩和は別の安全機構(Claude Code 側 auto mode classifier)に正しくブロックされる、という二重の制約があり、今回のようなガード検証タスクでは「実フック経由」の一次証跡は直接スクリプト起動に頼るのが現実的だと分かった。

やったこと(発火ログ・外部複製・override 検知・BLOCK メッセージへの行動規約埋め込み / Track C-9・issue #78)

Section titled “やったこと(発火ログ・外部複製・override 検知・BLOCK メッセージへの行動規約埋め込み / Track C-9・issue #78)”
  • 着手前に前提(#75 のガード第2世代・#74 の policy.md §8/§9)を実ファイルで確認。両ガードスクリプトは想定通りクォート除去・トークン照合・symlink 解決・--file モード対応済み、policy.md も §8「ブロック時の行動規約」・§9「override 規約」まで存在することを確認して着手。
  • .ai-harness/scripts/guard_lib.sh を新設し、check_dangerous_command.shcheck_sensitive_path.sh の両方から source する共通ロジックとして次を実装:
    • guard_log_event <kind> <target> <reason>: .ai-harness/logs/guard.log(タブ区切り: UTC タイムスタンプ・エージェント種別・判定種別 BLOCK/OVERRIDE/WARN・対象コマンド or パス・該当パターン)への追記と、同じ内容を macOS 統一ログへ logger -t ai-harness-guard で複製する処理を1関数に集約。
    • 簡易ローテーション: 追記後に行数が GUARD_LOG_MAX_LINES(既定 5000)を超えたら tail で古い行を切り詰め。
    • guard_override_requested [<command>]: 環境変数 HARNESS_ALLOW=1、またはコマンド文字列に前置された HARNESS_ALLOW=1 トークンを検知(可否判断はしない。policy.md §9 に従いユーザー指示前提)。
  • check_dangerous_command.sh: block()/warn()guard_log_event 呼び出し経由にし、block() は override 検知時に BLOCK ではなく OVERRIDE として記録し exit 0 で通過させるよう変更。BLOCK メッセージ末尾に policy.md §8 の定型文言「ガードに deny された操作は言い換えて再試行せず、§6 方針転換契約に従ってユーザーに相談する。」をそのまま追加。
  • check_sensitive_path.sh: match_patterns() を「理由を $MATCH_REASON にセットするだけ」に変更し、ログ記録・メッセージ出力・override 判定は check_one() に一本化(symlink 実体側の再判定と合わせた二重ログを防止)。同様に BLOCK 末尾へ §8 定型文言を追加し、override 検知時は OVERRIDE 記録+exit 0。
  • harness_doctor.sh の手動確認項目に、統一ログ側の発火記録を確認するコマンドを追記: log show --predicate 'process == "logger" AND eventMessage CONTAINS "ai-harness-guard"' --last 1h(zsh では log がビルトインと衝突するため /usr/bin/log を使う旨も明記)。
  • .ai-harness/tests/guard-corpus/run_guard_tests.sh に override 検知 5 件(env 変数・コマンド前置・非 override 時の通常 BLOCK・機密パス側の override/非 override)と、guard.log に BLOCK/OVERRIDE/WARN の3種別が実際に記録されていることを確認する3件を追加。既存91件+新規8件=99件全件パスを確認(実行結果はこのissueのコメントにも記録)。
  • 既存呼び出し元を grep -rl check_dangerous_command.sh / grep -rl check_sensitive_path.sh で確認したところ、テストランナー・journal 記述以外からは相変わらずどこからも呼ばれておらず(配線は別issue予定のまま)、破壊的変更の懸念なし。
  • macOS の logger -t <tag> は BSD syslog 互換の -t タグ引数を受け付けるが、統一ログ(log show)側の subsystem/category フィールドには反映されない(--style json で確認)。eventMessage 本文にも tag 文字列は自動挿入されないため、log show --predicate 'eventMessage CONTAINS "..."' で確実に拾うにはメッセージ本文自体にタグ文字列を埋め込む必要がある(ai-harness-guard: ... を先頭に置いた)。
  • zsh には log(対数計算)というビルトインが存在し、対話シェルで素の log show ... を打つと too many arguments という紛らわしいエラーになる。/usr/bin/log を明示するか command log を使う必要があり、doctor の手動確認コマンドにも注記した。
  • check_sensitive_path.shmatch_patterns() は元々 echo で直接メッセージを出しつつ return 0 していたが、symlink 実体側の再判定と override 判定を1箇所(check_one())に集約する都合上、「マッチ有無+理由だけを返す」関数に切り替える必要があった。ログ・メッセージ出力の一本化は、後から override や統計を差し込む際の見通しを良くする副次効果があった。
Section titled “やったこと(ガード第2世代: クォート除去・インタープリタ再帰・トークン照合・symlink 解決・テストコーパス / Track C-8・issue #75)”
  • 着手前に「状態」節を実環境で突き合わせ、1点(bash -c 'rm -rf /' は「素通り」ではなく現行実装では過剰BLOCKされる)を issue #75 にコメントで訂正した上で着手。
  • check_dangerous_command.sh を大幅改修:
    • クォート('...'/"...")内リテラルを除去した版(strip_quotes)で本体判定し、git commit -m "block rm -rf"grep "rm -rf" . の誤爆を解消。
    • 除去後もなお sh -c/bash -c/zsh -c/eval/xargs という実インタープリタ呼び出しが残っている場合は、除去前の生コマンドも重ねて判定(bash -c 'rm -rf /' は引き続き BLOCK)。
    • 追加危険パターン: find ... -deletegit clean -fd--dry-run/-n は除外)、shred、汎用 | sh 系(curl/wget 以外の出力も含む)。
    • コマンド文字列をトークン分割し、各トークンを check_sensitive_path.sh に委譲(cat .envecho $KEY > .envtee .envcp backup secrets/x を検知)。
    • 二段迂回対策: bash <file>/sh <file>/zsh <file>/source <file>/. <file> を検知した場合、対象ファイルの中身を読んで判定。加えて対象ファイルが git 管理外・未コミットなら WARN(exit 3、アダプタ側で ask に振り分ける想定)。
    • --file <path> モードを新設(書込み前コンテンツ、tool_input.content 相当の判定入口)。
    • exit code プロトコル(0=pass/2=block/3=warn)とフェイルクローズ規約(0/2/3 以外は deny 扱い)をヘッダに明記。
  • check_sensitive_path.sh: 実在するパスは realpath/readlink -f で物理解決し、symlink 経由で機密パスに実体が到達しているケースも検知するよう改修。
  • .ai-harness/tests/guard-corpus/commands_block.txt/commands_pass.txt/paths_block.txt/paths_pass.txt)と run_guard_tests.sh を新設。静的コーパス(91件)に加え、symlink 解決・二段迂回のファイル内容判定・git 管理外/未コミット WARN・--file モードを一時 git リポジトリ上で個別検証。全件パス確認。
  • 既存呼び出し元を grep -rl で確認したところ、この2スクリプトは #64 新設時点からどこからも呼ばれておらず(配線は #65 予定)、破壊的変更の懸念なし。
  • bash 3.2(macOS 既定)は「末尾改行なしの最終行」を while IFS= read -r line; do ...; done ループで読み飛ばす(body が一度も実行されない)。テストランナーで printf '%s' "$line" | checker としていたら check_sensitive_path.sh の stdin モードが全滅し、原因調査に手間取った。printf '%s\n' で末尾改行を必ず付ける形に直して解決。
  • 「クォート除去で誤爆を直す」と「インタープリタ引数の中身は除去しない」は一見矛盾する要求に見えるが、strip_quotes した版と生コマンド版の二段構え(生コマンド側の再判定は「除去後になおインタープリタ呼び出しが残っているか」をトリガーにする)にすることで、単純な文字列操作だけで両立できた。ペイロード抽出のような複雑な正規表現キャプチャは不要だった。

やったこと(policy.md 追補: 自己保護予告・行動規約・override・残余リスク / Track C-7・issue #74)

Section titled “やったこと(policy.md 追補: 自己保護予告・行動規約・override・残余リスク / Track C-7・issue #74)”
  • 起票時の「状態」(policy.md は §1〜7 まで存在)を実ファイルと突き合わせ、記載どおりであることを確認したうえで着手。
  • .ai-harness/policy.md に新設・追記した節:
    • §2.1 将来の書込み対象追加予告(既存 §2 の直後): .ai-harness/scripts/policy.md 自身・各エージェントのリポジトリ層設定ファイルは、C-10 で書込み deny の対象に追加する予定であることを明記(現時点ではまだ deny 化していない)。
    • §8 ブロック時の行動規約: ガードに deny されたら言い換えて再試行せず、§6 方針転換契約に従いユーザーに相談する旨を明記。後続 issue #78 が引用しやすいよう、1行の定型文を blockquote で用意した。
    • §9 override 規約: HARNESS_ALLOW=1 はユーザーがこの会話で明示指示した場合のみ使用可、Claude Code では override が必要な場面は設計上 ask に落ちる旨を明記。
    • §10 ガードの限界と残余リスク: ローカルガードは同権限のため完全阻止は原理的に不可能であること、深さ2以上の二段迂回・base64 等のエンコード迂回・シェル変数展開迂回(R=rm; $R -rf)は機構でなく文章契約でカバーする方針を明記。
    • §11 成功指標: トラック完了ごと/月次で guard.log をレビューし、誤爆が続くパターンは削除、ガード追加は事故駆動とする方針を明記。
    • §12 事故駆動の段階的強化: 段階1(Claude sandbox 機能)→段階2(高リスク issue のみ devcontainer 隔離)→段階3(フル分離)の順序表を追加。予防的な前倒しはしない旨も明記。
  • 節番号は §1〜7(既存)→ §8〜12(新設)で連番になっており、ずれがないことを確認済み。
  • §8 のブロックメッセージ想定文言は、後続 issue #78 が BLOCK メッセージの stderr にそのまま転記して一致させる計画のため、blockquote 形式で1文だけ独立させ、後から引用しやすい形にした。
  • §2.1 は「予告」であり、本 issue では deny 化(実際のガード改修)は行っていない(docs only)。deny 化自体は別トラック(C-10)の範囲。

やったこと(Claude 機構配線: permissions.deny + PreToolUse アダプタ / Track C-3・issue #65)

Section titled “やったこと(Claude 機構配線: permissions.deny + PreToolUse アダプタ / Track C-3・issue #65)”
  • 着手前に「前提」「状態」を実環境(.claude/settings.json・#75/#78 のガードスクリプト・guard_lib.sh)と突き合わせ、記載どおりであることを確認して着手(訂正コメント不要)。
  • 公式docs確認curl -L https://code.claude.com/docs/en/hooks.md の生テキストで実施。要約経由の WebFetch は取得のたびに内容がぶれたため、最終的な確証は生テキストの grep/Read で取った):
    • PreToolUsetool_input は Bash=command、Write=file_path+content、Edit=file_path+old_string+new_string(Edit に content フィールドは無い)。
    • 出力は hookSpecificOutput.permissionDecisionallow/deny/ask/defer)+permissionDecisionReasonask はユーザーに確認ダイアログを表示し、Claude はユーザーの応答を待つ。
    • permissions.deny/ask ルールは PreToolUse フックの返り値に関わらず必ず評価される(フックの deny は allow ルールに優先。フックの allow/ask は deny ルールをバイパスしない)。
    • フック設定のセッション開始時スナップショット仕様: issue 起票時の想定(「セッション中の書換えは反映されない」)は、現行の公式docsの記述とは食い違う。hooks.md の “Disable or remove hooks” 節に「Direct edits to hooks in settings files are normally picked up automatically by the file watcher.」という記述があり、hooks を含む設定変更は原則ファイルウォッチャーにより自動反映される(“normally” という留保はあるが、ConfigChange フックイベントで設定変更自体をブロック/監査する仕組みまで用意されている=反映されること自体が前提の設計)。よって「Claude では有効」(セッション開始時スナップショットで書換えが無視される)という前提は事実と異なると判断し、残余リスクとして記録する: 本アダプタの配線自体はファイルウォッチャーにより当該セッション中にも反映され得るため、旧フックのままでの安全動作を前提にした運用はできない(=配線直後から新フックが効き得る点を運用上考慮する)。
  • 実装:
    • .claude/settings.jsonpermissions.deny: ["Read(.env)", "Read(**/.env.*)"] を追加(update-config スキル経由、既存 hooks.Stop/enabledPlugins を非破壊でマージ)。Read(./.env) ではなく bare filename 形式 Read(.env) を採用: 公式docsで bare filename は gitignore semantics で任意の深さにマッチすると確認したため(Read(./.env) はカレント直下限定)。PreToolUse(Read)は今回の新規アダプタの対象外のため、機密ファイル Read を止められるのはこの宣言的ルールのみ(設計判断8の趣旨どおり最小セットに絞りつつ実効性を優先)。
    • .ai-harness/scripts/hooks/claude-pretooluse.sh を新設。stdin JSON から tool_name/tool_input.file_path/tool_input.command/tool_input.content(Edit は new_string で代替)を抽出し、check_dangerous_command.sh/check_sensitive_path.sh へ渡す。終了コード 0/2/3 を permissionDecision の allow相当/deny/ask に写像。
    • fail-closed: jq 不在、stdin JSON パース失敗、ガードスクリプトの 0/2/3 以外の終了コードはすべて deny。
    • OVERRIDE→ask 変換: guard_lib.shguard_override_requested をアダプタ側でも独立に呼び、HARNESS_ALLOW=1 検知時はガードスクリプトの終了コード(override 時は内部で 0 に丸められる)に関わらず ask を返す(policy.md §9 の「Claude Code では override は ask に帰着する」設計をここで実現)。フェイルクローズは override 変換より優先(想定外終了時は override 有無を問わず deny)。
    • 未コミット/git 管理外スクリプト実行の WARN(exit 3)は ask に振り分け。
    • .claude/settings.jsonhooks.PreToolUse に matcher Edit|WriteBash の2グループを配線(同一アダプタを共有)。
  • 検証: .ai-harness/tests/guard-corpus/run_guard_tests.sh 実行(99件中0件失敗)。加えて実フック(CLAUDE_PROJECT_DIR を設定した状態で .claude/settings.json に記載のコマンドをそのまま実行)経由で .env への Write と rm -rf を含む Bash がそれぞれ deny されること、git status のような安全な操作は決定なし(=許可相当)で通ることを個別に確認。guard.log にも該当 BLOCK 行が記録されていることを確認。
  • ガードスクリプト自身(check_dangerous_command.sh/check_sensitive_path.sh)は HARNESS_ALLOW=1 検知時に BLOCK を「OVERRIDE」として自ら exit 0 に丸めてしまうため、アダプタが終了コードだけを見て判定すると override 時に自動許可(silent allow)になってしまう。これを防ぐため、アダプタ側で override 要求の有無を独立に再判定する必要があった——「ガードスクリプトの返り値をそのまま transport する」だけでは policy.md §9 の要件(override は ask に帰着させる)を満たせない、という設計上の落とし穴。
  • WebFetch(要約モデル経由)は同一URLに対して「Security considerations 節は存在しない」「セッション開始時にスナップショットされる」等、取得のたびに異なる/不正確な要約を返した。今回のような契約上重要な確認(フック仕様等)は curl -L <url>.md で生の Markdown を取得し、grep/Read で直接原文にあたる方が確実だった。

やったこと(Cursor 機構配線: pre 系フック / Track C-4・issue #66)

Section titled “やったこと(Cursor 機構配線: pre 系フック / Track C-4・issue #66)”
  • 着手前に依存 #75・#78 のクローズ状況、.cursor/hooks.jsonstop のみ配線)、cursor-stop.sh の入出力規約、ガードスクリプト(check_dangerous_command.sh/check_sensitive_path.sh/guard_lib.sh)の exit code プロトコル(0=pass/2=block/3=warn)を実環境で確認。「状態」節と齟齬なし。
  • Cursor 公式 docs(cursor.com/docs/hooks)を複数回 WebFetch で確認し、記憶で書かずに次を裏取りした:
    • pre 系イベントは beforeShellExecutionbeforeMCPExecutionbeforeReadFilepreToolUse(generic)が存在し、いずれも permission 出力で deny 可能。
    • preToolUse の出力仕様に「"ask" はスキーマ上受理されるが today は enforce されない」と明記されている。
    • beforeShellExecution/beforeMCPExecution/beforeReadFile は hooks.json 側で failClosed: true を設定可能(既定は fail-open)。
    • コミュニティのバグ報告(forum.cursor.com、2025-12 オープン継続)で beforeShellExecution"allow"/"ask" が allowlist 優先で無視される既知の不具合を確認。"deny" のみ確実に機能すると報告されている。
    • 結論: Cursor には信頼できる ask 相当が無いと判断(issue完了条件の該当分岐)。
  • .ai-harness/scripts/hooks/cursor-pretool.sh を新規作成し、.cursor/hooks.jsonbeforeShellExecution(無条件)と preToolUse(matcher: Write|Edit|Delete, failClosed: true)を追加配線。既存 stop 配線は変更なし。
    • beforeShellExecution: command フィールドを check_dangerous_command.sh へ渡す。
    • preToolUse: tool_input.file_pathcheck_sensitive_path.sh へ、実行可能ファイル(shebang or .sh/.bash/.zsh)への書込みに限り tool_input.content を一時ファイル経由で check_dangerous_command.sh --file へ渡す(tool_input.content の存在は公式未文書化のため、無い場合は file_path 単体判定に degrade)。
    • exit 0→{"permission":"allow"}、exit 2→{"permission":"deny",...}、exit 3(WARN)→{"permission":"ask",...}(degrade。下記参照)、想定外の exit code・jq 不在→fail-closed で deny
  • degrade 範囲(完了条件どおりスクリプトコメント + 本コメント + issue #66 コメントに明記): HARNESS_ALLOW override 検知・未コミットスクリプト実行検知(WARN/exit 3)は BLOCK にせず permission:"ask" を best-effort 出力するのみで、実際の抑止力(ユーザー確認ダイアログ表示)は保証しない。実効的な担保は guard.log への OVERRIDE/WARN 記録(issue #78)+ policy.md §8(ブロック時の行動規約)/§9(override 規約)の文章契約に置く。
  • スモークテスト: run_guard_tests.sh 実行(99件中0件失敗)+ 実フック経由(cursor-pretool.sh に stdin JSON を流す)で以下を確認: beforeShellExecutionrm -rf deny・安全コマンド allow、preToolUse.env 書込み deny・通常ファイル allow・.sh への危険内容書込み deny、未追跡スクリプト実行の WARN→ask degrade、jq 不在時の fail-closed deny。guard.log に BLOCK/WARN が実際に記録されることも確認。
  • beforeShellExecution の入力 JSON の command は「そのままのコマンド文字列」であり、cd X && bash Y のような複合コマンドを渡しても、ガード自身は実際に cd した状態を再現するわけではない(テキストとして正規表現照合するだけ)。スモークテストで bash safe.sh(相対パス)の WARN 判定を検証する際、テスト実行側のシェル cwd を対象ディレクトリに実際に cd しておく必要があった(run_guard_tests.sh も同じ前提で書かれている)。
  • 公式ドキュメントの記述と実運用のギャップ(beforeShellExecutionask/allow がスキーマ上は存在してもコミュニティ報告では allowlist 優先で無視される)は、“スキーマに書いてあるか” だけでなく “実際に enforce される報告があるか” まで一次情報(フォーラムのバグ報告)で裏取りする必要があることを示す一例だった。

やったこと(Track R プラン文書化・issue 起票)

Section titled “やったこと(Track R プラン文書化・issue 起票)”
  • Writer エージェントが push した PR を 別セッション・別モデルの Reviewer がレビューする仕組み(Track R)のプランを文書化し GitHub issue に分割起票した。
  • プラン正本: .ai-harness/plan-track-r-agent-review.md(/clear 後もここから追跡可能)
  • トラッキング issue: #89 Track R: プッシュ後の別エージェントレビュー(ピン留め済み → 後日クローズ)
  • 子 issue: #84(R-0 契約・スキル), #85(R-1 手動 MVP), #86(R-2 orchestrator), #87(R-3 GitHub Actions), #88(R-4 babysit ループ)
  • AGENTS.md ハーネス節に Track R プランへのリンクを追加

やったこと(Track R-0: レビュー契約 + review-after-push スキル / issue #84)

Section titled “やったこと(Track R-0: レビュー契約 + review-after-push スキル / issue #84)”
  • skills/review-after-push/SKILL.md を新設: Writer handoff(draft PR + needs-agent-review)、Reviewer readonly 手順、Severity/Location/Finding 出力表、worktree 分離。
  • .ai-harness/contracts/agent-review.yaml を新設: archived pr-stabilizer から diff budget / forbidden paths / Reviewer 禁止操作を移植。Writer≠Reviewer・別モデル必須を明文化。
  • AGENTS.md に Writer/Reviewer 分離節を追加(worktree 節と接続)。
  • harness_doctor.shskills/review-after-push/SKILL.md 存在チェックを追加。
  • .agents/skills/review-after-push symlink を作成。
  • #85: 手動 /review-pr MVP

やったこと(Track R-1: 手動 /review-pr MVP / issue #85)

Section titled “やったこと(Track R-1: 手動 /review-pr MVP / issue #85)”
  • .ai-harness/scripts/review_worktree.sh を新設: PR 番号から .worktrees/review-pr-<N>/ を自動 checkout(gh pr view で head 取得)。
  • skills/review-after-push/references/manual-review-pr.md — 手動 MVP 手順(トリガー、worktree、diff、gh pr comment、readonly 確認)。
  • skills/review-after-push/SKILL.md に Manual MVP 節を追加。Reviewer workflow を review_worktree.sh 参照に更新。
  • AGENTS.mdreview_worktree.sh 一行追記。
  • テスト走: PR #91/review-pr 91 手順でレビュー。.worktrees/review-pr-91/ で diff 確認 → レビューコメント投稿。Reviewer worktree は git status クリーン(commit/push なし)。
  • #86: orchestrator 半自動レビュー委譲

やったこと(Track R-2: orchestrator 半自動レビュー委譲 / issue #86)

Section titled “やったこと(Track R-2: orchestrator 半自動レビュー委譲 / issue #86)”
  • skills/plan-to-issues/SKILL.mdReview 委譲(Track R / orchestrator) 節を追加: Writer handoff(PR + needs-agent-review + Writer モデル)、gh pr list --label needs-agent-review、Reviewer 委譲プロンプト契約。
  • skills/plan-to-issues/references/reviewer-delegation-prompt.md — orchestrator → Reviewer 委譲テンプレート。
  • Writer 完了チェックリスト: plan-to-issues 完了時節 + .github/ISSUE_TEMPLATE/agent-task.md に Track R handoff 追記。
  • review_worktree.sh 修正: issue worktree 内から実行しても .worktrees/review-pr-<N>/ がリポジトリ直下に作られるよう git-common-dir で ROOT 解決。
  • E2E 試走(Writer 模擬 → orchestrator → Reviewer):
    1. PR #92needs-agent-review + <!-- writer-handoff: issue=#85 model=composer-2.5 -->
    2. gh pr list --label needs-agent-review で検出
    3. Reviewer readonly レビュー → コメント
    4. needs-agent-review 除去、agent-reviewed 付与。review worktree クリーン。
  • #87: GitHub Actions レビュートリガー

やったこと(Track R-3: GitHub Actions レビュートリガー / issue #87)

Section titled “やったこと(Track R-3: GitHub Actions レビュートリガー / issue #87)”
  • .github/workflows/agent-review.yml を新設: pull_request(opened/synchronize/ready_for_review)+ workflow_dispatch。条件は needs-agent-review ラベルまたは PR 本文 Review-Request: auto
  • MVP 方式: Cursor Cloud 自動起動は secrets 未整備のため bot コメントで dispatch チェックリスト投稿(Reviewer 本体は Actions 外 — #85/#86 と同様)。
  • skills/review-after-push/SKILL.md に CI トリガー節を追記。
  • 発火テスト: PR #94(本文 Review-Request: auto)→ workflow run 28869305116 success → dispatch コメント
  • #88: babysit レビューループ(任意)

やったこと(Track R-4: babysit レビューループ / issue #88)

Section titled “やったこと(Track R-4: babysit レビューループ / issue #88)”
  • skills/review-after-push/SKILL.mdBabysit ループ 節(終了条件・label 遷移・再 handoff)。
  • skills/review-after-push/references/babysit-loop.md — 状態機械・ループ手順詳細。
  • skills/babysit/SKILL.md 新設 + .agents/skills/babysit symlink(Cursor 組み込み babysit の Track R ラッパー)。
  • .ai-harness/contracts/agent-review.yamllabel_state_machine 追記。
  • ループ試走(指摘 → 修正 → 再レビュー):
    1. Reviewer round 1: PR #94 に Minor 指摘(babysit 節未整備)— comment
    2. Writer fix: 本コミットで babysit ループ・skills/babysit/ 追加 · needs-agent-review 再 handoff
    3. Reviewer round 2: PR #95 blocker 0 → agent-reviewedre-review comment
  • Track R 完了。次トラック(Track K #58 等)へ

やったこと(Track R スタック main マージ + プラン doc PR #90)

Section titled “やったこと(Track R スタック main マージ + プラン doc PR #90)”
  • PR #91–#95 を main に順次マージ(#84–#88 実装)。
  • tracking #89 クローズ。
  • PR #90: .ai-harness/plan-track-r-agent-review.md を main に追加(完了状態に更新 · AGENTS.md プランリンク · journal 統合)。