Skip to content

feat(hooks): Post-PR Monitor — CI・CodeRabbit自動監視 - #10

Merged
aloekun merged 1 commit into
masterfrom
feat/post-pr-monitor
Apr 2, 2026
Merged

feat(hooks): Post-PR Monitor — CI・CodeRabbit自動監視#10
aloekun merged 1 commit into
masterfrom
feat/post-pr-monitor

Conversation

@aloekun

@aloekun aloekun commented Apr 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • check-ci-coderabbit exe: CI (GitHub Actions) + CodeRabbit レビュー状態を gh CLI 経由でチェックするスタンドアロン checker (37テスト)
  • hooks-post-pr-monitor exe: PostToolUse hook で gh pr create / git push / jj git push を検出し、CronCreate で定期監視を開始 (29テスト)
  • hooks-config.toml: [post_pr_monitor] セクション追加(ポーリング間隔、最大監視時間、CI/CodeRabbit 個別有効化)
  • ADR-009: 設計判断を文書化

レビュー指摘対応 (Critical 1件 + High 10件)

  • Critical: `utc_now_iso8601()` fallback を Howard Hinnant アルゴリズムで正しい ISO 8601 に
  • High: GraphQL を -F 変数渡し (インジェクション防止)、run_gh デッドロック防止 (wait_with_output)、入力値検証追加
  • High: checker_path 二重エスケープ除去、issues API エンドポイント修正、dead code 削除
  • High: serde_json/stdin/regex パースエラーログ追加、auto_detect 失敗理由を summary に反映

Test plan

  • cargo test — check-ci-coderabbit 37テスト通過
  • cargo test — hooks-post-pr-monitor 29テスト通過
  • pnpm build:hooks — 全 exe ビルド成功
  • pnpm push — push パイプライン (test → review → push) 通過
  • 実際の PR で PostToolUse hook 発火 → CronCreate → check → 報告の E2E 確認

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新機能

    • PR作成やコードプッシュを検出して、指定期間・間隔で自動的にCIとCodeRabbitの状態を監視し、JSON出力でフォローアップ指示を返すポストPRモニタを追加しました。
    • モニタが検出した場合、自動でチェック実行をスケジュールして継続監視します。
  • ドキュメント

    • ADR-009(Post-PR Monitor)の設計文書を追加しました。
  • Chores

    • フック用ビルド/デプロイ設定と.gitignoreを更新し、関連バイナリのビルド・配備を組み込みました。

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

PRは、プッシュ/PR作成後に自動でCIとCodeRabbitを監視するワークフローを導入するため、2つのRust実行ファイルと関連設定・ビルド・デプロイ・ドキュメントを追加します(フック検出→Cron登録→定期チェック→JSON結果出力)。

Changes

Cohort / File(s) Summary
Check-CI-CodeRabbit Rust Tool
.claude/check-ci-coderabbit/Cargo.toml, .claude/check-ci-coderabbit/src/main.rs
新規バイナリ。gh CLIでCI実行・CodeRabbitコメント/スレッドを取得・解析し、JSON出力(決定ロジック・多数のユニットテスト含む)。レビュー注意点:決定ロジックとgh出力パースの堅牢性。
Post-PR Monitor Hook
.claude/hooks-post-pr-monitor/Cargo.toml, .claude/hooks-post-pr-monitor/src/main.rs
新規PostToolUseフック。コマンドトリガー検出、hooks-config読み取り、CronCreate向け追加コンテキスト生成を行う。レビュー注意点:トリガー正規表現・config読み取り・パス解決。
Hook Configuration & Templates
.claude/hooks-config.toml, templates/hooks-config-python.toml, templates/hooks-config-typescript.toml
[post_pr_monitor]セクションを追加/テンプレートにコメント付き例を追加。レビュー注意点:デフォルト設定とドキュメント整合性。
Settings & Registration
.claude/settings.local.json.template
PostToolUseに追加フック登録(Bashマッチャ、タイムアウト)。確認点:既存マッチャとの競合。
Build & Deployment
package.json, scripts/deploy-hooks.ts
2つのビルドスクリプト追加、build:hooksチェーン更新、デプロイ対象に新EXE追加。確認点:ビルドコピー先・CIパイプライン影響。
Docs & Ignore
.gitignore, CLAUDE.md, docs/adr/adr-009-post-pr-monitor.md
実行ファイルを.gitignoreへ追加、ADR-009を追加(モニタリング設計を記載)。確認点:ADRと実装仕様の一致。

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Claude
    participant Hook as hooks-post-pr-monitor.exe
    participant Scheduler as CronCreate
    participant Monitor as check-ci-coderabbit.exe
    participant GH as GitHub CLI/API

    User->>Claude: git push / gh pr create
    Claude->>Hook: PostToolUse event (tool_input.command)
    Hook->>GH: gh pr view / gh repo view (suppress stderr)
    GH-->>Hook: repo, pr, timestamp
    Hook->>Scheduler: emit CronCreate (additionalContext with checker cmd)
    Scheduler->>Monitor: invoke checker (--push-time, --repo, --pr)
    Monitor->>GH: gh run list / statuses / GraphQL threads / comments
    GH-->>Monitor: CI runs, commit statuses, comments, threads
    Monitor-->>Claude: JSON {status, action, ci, coderabbit, summary}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main feature: a post-PR monitoring system for CI and CodeRabbit reviews. The title is concise and directly matches the primary changes in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 86.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
.claude/check-ci-coderabbit/src/main.rs (2)

608-621: 引数エラー時の出力形式について確認を推奨します。

--push-time が欠けている場合、JSON ではなく stderr にエラーメッセージを出力して exit(1) します。hooks-post-pr-monitor が常に --push-time を渡すため実運用上は問題ありませんが、手動デバッグ時やエラー発生時の一貫性のため、エラー時も JSON 形式で出力する選択肢も検討できます。

💡 エラー時も JSON を出力する例
fn main() {
    let args = match parse_args() {
        Ok(a) => a,
        Err(e) => {
            let result = CheckResult {
                status: "error".to_string(),
                action: "stop_monitoring_failure".to_string(),
                ci: CiStatus { overall: "error".to_string(), runs: vec![] },
                coderabbit: CodeRabbitStatus { review_state: "error".to_string(), ..Default::default() },
                summary: format!("引数エラー: {}", e),
            };
            let json = serde_json::to_string_pretty(&result).unwrap_or_else(|_| "{}".to_string());
            println!("{}", json);
            std::process::exit(1);
        }
    };
    // ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/check-ci-coderabbit/src/main.rs around lines 608 - 621, When
parse_args() fails in main, instead of printing a plain stderr string and
exiting, build a CheckResult error payload and print it as JSON so error output
stays consistent; update the Err(e) arm in main (where parse_args() is matched)
to construct a CheckResult with status or overall marked "error", populate
CiStatus and CodeRabbitStatus with minimal error indicators, set summary to
include the parse error message, serialize with serde_json::to_string_pretty and
println! the JSON, then exit(1). Reference parse_args, main, CheckResult,
CiStatus, and CodeRabbitStatus to locate and modify the error-handling branch.

74-91: タイマースレッドの join により不要な待機が発生する可能性があります。

gh コマンドが素早く完了した場合でも、timer.join() はタイマースレッドが 30 秒の sleep を完了するまで待機します。実用上は CronCreate ジョブ全体のタイムアウトがあるため大きな問題にはなりませんが、改善の余地があります。

♻️ タイマースレッドを join しない方法
-    // タイマースレッドを停止 (join するが結果は無視)
-    let _ = timer.join();
+    // タイマースレッドは join しない (プロセス終了時に自動終了)
+    drop(timer);

または、std::sync::Condvar を使用してタイマースレッドに早期終了を通知する方法もあります。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/check-ci-coderabbit/src/main.rs around lines 74 - 91, The timer
thread (timer spawned with std::thread::spawn using flag_clone) is being joined
after child.wait_with_output, which can block up to the timer sleep duration;
instead make the timer truly detachable and cooperative: remove the blocking
timer.join() and change the timer closure to poll an Arc<AtomicBool>
(flag/flag_clone) in short sleep increments (or wait on a Condvar/channel) so it
exits early when the main thread sets the flag after child.wait_with_output
returns; keep the existing kill logic (taskkill) in the timer when the timeout
elapses, but do not join timer — let it exit on its own when the flag is set.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.claude/check-ci-coderabbit/src/main.rs:
- Around line 608-621: When parse_args() fails in main, instead of printing a
plain stderr string and exiting, build a CheckResult error payload and print it
as JSON so error output stays consistent; update the Err(e) arm in main (where
parse_args() is matched) to construct a CheckResult with status or overall
marked "error", populate CiStatus and CodeRabbitStatus with minimal error
indicators, set summary to include the parse error message, serialize with
serde_json::to_string_pretty and println! the JSON, then exit(1). Reference
parse_args, main, CheckResult, CiStatus, and CodeRabbitStatus to locate and
modify the error-handling branch.
- Around line 74-91: The timer thread (timer spawned with std::thread::spawn
using flag_clone) is being joined after child.wait_with_output, which can block
up to the timer sleep duration; instead make the timer truly detachable and
cooperative: remove the blocking timer.join() and change the timer closure to
poll an Arc<AtomicBool> (flag/flag_clone) in short sleep increments (or wait on
a Condvar/channel) so it exits early when the main thread sets the flag after
child.wait_with_output returns; keep the existing kill logic (taskkill) in the
timer when the timeout elapses, but do not join timer — let it exit on its own
when the flag is set.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5281045c-71c8-4a6b-848b-d731974637f7

📥 Commits

Reviewing files that changed from the base of the PR and between b86d590 and 120d2d3.

⛔ Files ignored due to path filters (2)
  • .claude/check-ci-coderabbit/Cargo.lock is excluded by !**/*.lock
  • .claude/hooks-post-pr-monitor/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .claude/check-ci-coderabbit/Cargo.toml
  • .claude/check-ci-coderabbit/src/main.rs
  • .claude/hooks-config.toml
  • .claude/hooks-post-pr-monitor/Cargo.toml
  • .claude/hooks-post-pr-monitor/src/main.rs
  • .claude/settings.local.json.template
  • .gitignore
  • CLAUDE.md
  • docs/adr/adr-009-post-pr-monitor.md
  • package.json
  • scripts/deploy-hooks.ts
  • templates/hooks-config-python.toml
  • templates/hooks-config-typescript.toml

- H1: GraphQL を -F 変数渡しに変更 (インジェクション防止)
- H2: run_gh デッドロック防止 (try_wait → wait_with_output + タイムアウトスレッド)
- H3: repo/head_sha の入力値検証追加
- H4: serde_json パースエラーの eprintln ログ追加 (4箇所)
- H5: post-pr-monitor stdin/JSON エラーログ追加
- H6: 無効な正規表現パターンの警告ログ追加
- H7: auto_detect 失敗理由を summary に反映
- regex クレートを check-ci-coderabbit に追加

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aloekun
aloekun force-pushed the feat/post-pr-monitor branch from 120d2d3 to 6fad2aa Compare April 2, 2026 12:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
.claude/hooks-post-pr-monitor/src/main.rs (1)

100-114: 正規表現の毎回コンパイルは非効率

is_trigger_command はパターンごとに Regex::new(pat) を呼び出しています。デフォルトで 3 パターン × 毎回の hook 呼び出しでコンパイルが発生します。

♻️ コンパイル済み正規表現をキャッシュする修正案
+use std::collections::HashMap;
+use std::sync::Mutex;
+use std::sync::LazyLock;
+
+static REGEX_CACHE: LazyLock<Mutex<HashMap<String, Regex>>> =
+    LazyLock::new(|| Mutex::new(HashMap::new()));
+
+fn get_or_compile_regex(pattern: &str) -> Option<Regex> {
+    let mut cache = REGEX_CACHE.lock().ok()?;
+    if let Some(re) = cache.get(pattern) {
+        return Some(re.clone());
+    }
+    match Regex::new(pattern) {
+        Ok(re) => {
+            cache.insert(pattern.to_string(), re.clone());
+            Some(re)
+        }
+        Err(e) => {
+            eprintln!("[post-pr-monitor] 無効な正規表現パターン \"{}\": {}", pattern, e);
+            None
+        }
+    }
+}
+
 fn is_trigger_command(command: &str, patterns: &[String]) -> bool {
     for pat in patterns {
-        match Regex::new(pat) {
-            Ok(re) => {
-                if re.is_match(command) {
-                    return true;
-                }
-            }
-            Err(e) => {
-                eprintln!("[post-pr-monitor] 無効な正規表現パターン \"{}\": {}", pat, e);
+        if let Some(re) = get_or_compile_regex(pat) {
+            if re.is_match(command) {
+                return true;
             }
         }
     }
     false
 }

または、detect_command_type と共通化してデフォルトパターンは LazyLock<Regex> で静的に保持することも検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/hooks-post-pr-monitor/src/main.rs around lines 100 - 114,
is_trigger_command currently compiles each pattern on every call via
Regex::new(pat) which is inefficient; fix it by precompiling and reusing Regex
objects instead: change the API to accept compiled patterns (e.g. &[Regex]) or
build a one-time cache of compiled Regexes and look them up (use
once_cell::sync::LazyLock or a static HashMap) so Regex::new is not called per
invocation; update callers (including detect_command_type) to pass the compiled
Regex slice or rely on the shared LazyLock defaults for the three default
patterns.
.claude/check-ci-coderabbit/src/main.rs (4)

276-280: ISO 8601 文字列比較の前提条件に注意

t > push_time による文字列比較は、両方のタイムスタンプが同一フォーマット(Zulu 時間、ミリ秒なし)であることを前提としています。GitHub API が 2026-04-01T12:00:00.123Z のようにミリ秒を含む場合、比較が不正確になる可能性があります。

現時点では GitHub API のレスポンスが一貫した形式を返すため実用上問題ないと思われますが、堅牢性のためにタイムスタンプのパースと数値比較を検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/check-ci-coderabbit/src/main.rs around lines 276 - 280, The current
string comparison (created_at.as_deref().map(|t| t > push_time)) can be wrong
when timestamps include milliseconds; instead parse both timestamps to chrono
DateTime (e.g., DateTime<Utc> via DateTime::parse_from_rfc3339 or
chrono::DateTime::parse_from_rfc3339) and compare the resulting DateTime values;
update the code around after_push_time, parsing push_time once to a DateTime
prior to the map, parse created_at inside the map (handle parse errors by
treating as false or logging), and add the chrono import and error handling so
comparisons are numeric/temporal rather than string-based.

495-495: .map(|r| r) は不要な identity 操作

repo_result.map(|r| r) は何もしていません。

♻️ 簡略化
-    let repo = repo_result.map(|r| r).unwrap_or_default();
+    let repo = repo_result.unwrap_or_default();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/check-ci-coderabbit/src/main.rs at line 495, repo_result.map(|r| r)
は不要な identity 操作なので単に unwrap_or_default を呼ぶように簡略化してください:置換対象は変数 repo
を作る式(repo_result.map(|r| r).unwrap_or_default() または類似のパターン)で、map(|r| r) を削除して
let repo = repo_result.unwrap_or_default(); としてください。

443-461: get_current_branch にタイムアウトがない

run_gh は 30 秒のタイムアウトを実装していますが、get_current_branchgit branch --show-current にはタイムアウトがありません。コメントで「CronCreate ジョブ全体にタイムアウトがあるため問題ない」と記載されていますが、一貫性のためにも run_gh と同様のタイムアウト機構を共通化することを検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/check-ci-coderabbit/src/main.rs around lines 443 - 461,
get_current_branch spawns git without a timeout, unlike run_gh which enforces
30s; add the same timeout behavior by extracting/using the existing run_gh (or
its underlying helper) to execute "git branch --show-current" so the call times
out consistently, or factor out a new helper (e.g., run_command_with_timeout)
and have both run_gh and get_current_branch call it; update get_current_branch
to return an error if the helper reports a timeout or nonzero exit, and keep the
same UTF-8/trimming logic on success.

470-473: Regex の毎回コンパイルは非効率

is_valid_repo は呼び出しごとに正規表現をコンパイルしています。この関数は run_check 内で複数回呼ばれる可能性があり、パフォーマンスに影響します。

♻️ `once_cell::sync::Lazy` または `std::sync::LazyLock` (Rust 1.80+) を使用した修正案
+use std::sync::LazyLock;
+
+static REPO_REGEX: LazyLock<regex::Regex> = LazyLock::new(|| {
+    regex::Regex::new(r"^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$").unwrap()
+});
+
 fn is_valid_repo(repo: &str) -> bool {
-    let re = regex::Regex::new(r"^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$").unwrap();
-    re.is_match(repo)
+    REPO_REGEX.is_match(repo)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/check-ci-coderabbit/src/main.rs around lines 470 - 473, The
is_valid_repo function currently compiles the regex on every call; replace that
with a static, precompiled Regex (e.g., using once_cell::sync::Lazy or
std::sync::LazyLock) so the pattern r"^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$" is
compiled once at startup and reused; update is_valid_repo to reference the
static (e.g., RE.is_match(repo)) and add the necessary use/import for Lazy and
regex::Regex.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/check-ci-coderabbit/src/main.rs:
- Around line 77-93: The timeout handler currently only kills the child on
Windows; update the thread closure where flag_clone.store(...) is called (the
std::thread::spawn block using done_clone, flag_clone and child_id) to also
terminate the child on Unix by adding a cfg(unix) branch that sends SIGKILL
(e.g., invoke "kill -9 <pid>" via Command or call nix::sys::signal::kill or
libc::kill) after setting the timeout flag, ensuring the gh child process is
terminated so wait_with_output doesn't block; alternatively document
Windows-only support if you choose not to implement Unix handling.

In @.claude/hooks-post-pr-monitor/src/main.rs:
- Around line 185-204: The current run_gh_quiet uses Windows-only
Command::new("cmd").args(["/c", "gh"]) which breaks on Linux/macOS; change it to
invoke gh directly like the run_gh in check-ci-coderabbit by replacing the
Windows wrapper with Command::new("gh").args(args) while preserving the
stdout/stderr piping, output().ok()? handling, and the existing logic that
returns Some(trimmed_string) on success or None on failure.
- Around line 283-295: Remove the test-only debug_log function and replace its
usages with eprintln! (or remove the calls if redundant): delete the debug_log
function definition and update all places that call debug_log(...) to either
call eprintln!("[post-pr-monitor] {}", ... ) with the same message expression or
remove those calls entirely so no file writes occur in production; search for
debug_log invocations to update each call site accordingly.

---

Nitpick comments:
In @.claude/check-ci-coderabbit/src/main.rs:
- Around line 276-280: The current string comparison
(created_at.as_deref().map(|t| t > push_time)) can be wrong when timestamps
include milliseconds; instead parse both timestamps to chrono DateTime (e.g.,
DateTime<Utc> via DateTime::parse_from_rfc3339 or
chrono::DateTime::parse_from_rfc3339) and compare the resulting DateTime values;
update the code around after_push_time, parsing push_time once to a DateTime
prior to the map, parse created_at inside the map (handle parse errors by
treating as false or logging), and add the chrono import and error handling so
comparisons are numeric/temporal rather than string-based.
- Line 495: repo_result.map(|r| r) は不要な identity 操作なので単に unwrap_or_default
を呼ぶように簡略化してください:置換対象は変数 repo を作る式(repo_result.map(|r| r).unwrap_or_default()
または類似のパターン)で、map(|r| r) を削除して let repo = repo_result.unwrap_or_default();
としてください。
- Around line 443-461: get_current_branch spawns git without a timeout, unlike
run_gh which enforces 30s; add the same timeout behavior by extracting/using the
existing run_gh (or its underlying helper) to execute "git branch
--show-current" so the call times out consistently, or factor out a new helper
(e.g., run_command_with_timeout) and have both run_gh and get_current_branch
call it; update get_current_branch to return an error if the helper reports a
timeout or nonzero exit, and keep the same UTF-8/trimming logic on success.
- Around line 470-473: The is_valid_repo function currently compiles the regex
on every call; replace that with a static, precompiled Regex (e.g., using
once_cell::sync::Lazy or std::sync::LazyLock) so the pattern
r"^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$" is compiled once at startup and reused;
update is_valid_repo to reference the static (e.g., RE.is_match(repo)) and add
the necessary use/import for Lazy and regex::Regex.

In @.claude/hooks-post-pr-monitor/src/main.rs:
- Around line 100-114: is_trigger_command currently compiles each pattern on
every call via Regex::new(pat) which is inefficient; fix it by precompiling and
reusing Regex objects instead: change the API to accept compiled patterns (e.g.
&[Regex]) or build a one-time cache of compiled Regexes and look them up (use
once_cell::sync::LazyLock or a static HashMap) so Regex::new is not called per
invocation; update callers (including detect_command_type) to pass the compiled
Regex slice or rely on the shared LazyLock defaults for the three default
patterns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a1083153-ff85-40ab-9155-b6d3e22fe399

📥 Commits

Reviewing files that changed from the base of the PR and between 120d2d3 and 6fad2aa.

⛔ Files ignored due to path filters (3)
  • .claude/check-ci-coderabbit/Cargo.lock is excluded by !**/*.lock
  • .claude/hooks-post-pr-monitor/Cargo.lock is excluded by !**/*.lock
  • .claude/post-pr-monitor-debug.log is excluded by !**/*.log
📒 Files selected for processing (13)
  • .claude/check-ci-coderabbit/Cargo.toml
  • .claude/check-ci-coderabbit/src/main.rs
  • .claude/hooks-config.toml
  • .claude/hooks-post-pr-monitor/Cargo.toml
  • .claude/hooks-post-pr-monitor/src/main.rs
  • .claude/settings.local.json.template
  • .gitignore
  • CLAUDE.md
  • docs/adr/adr-009-post-pr-monitor.md
  • package.json
  • scripts/deploy-hooks.ts
  • templates/hooks-config-python.toml
  • templates/hooks-config-typescript.toml
✅ Files skipped from review due to trivial changes (8)
  • CLAUDE.md
  • scripts/deploy-hooks.ts
  • templates/hooks-config-python.toml
  • .claude/check-ci-coderabbit/Cargo.toml
  • .gitignore
  • .claude/hooks-post-pr-monitor/Cargo.toml
  • templates/hooks-config-typescript.toml
  • docs/adr/adr-009-post-pr-monitor.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • .claude/settings.local.json.template
  • package.json

Comment on lines +77 to +93
std::thread::spawn(move || {
let deadline = std::time::Instant::now() + Duration::from_secs(30);
while std::time::Instant::now() < deadline {
if done_clone.load(std::sync::atomic::Ordering::Relaxed) {
return; // プロセス完了 → スレッド即終了
}
std::thread::sleep(Duration::from_millis(100));
}
// タイムアウト到達
flag_clone.store(true, std::sync::atomic::Ordering::Relaxed);
#[cfg(target_os = "windows")]
{
let _ = Command::new("taskkill")
.args(["/F", "/PID", &child_id.to_string()])
.output();
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

タイムアウト処理が Windows 専用になっている

タイムアウト発生時のプロセス強制終了が #[cfg(target_os = "windows")] ブロック内にのみ実装されています。Linux/macOS 環境では timeout_flag は設定されますが、gh プロセスは kill されず、wait_with_output がブロックし続ける可能性があります。

🔧 Unix 対応を追加する修正案
         // タイムアウト到達
         flag_clone.store(true, std::sync::atomic::Ordering::Relaxed);
         #[cfg(target_os = "windows")]
         {
             let _ = Command::new("taskkill")
                 .args(["/F", "/PID", &child_id.to_string()])
                 .output();
         }
+        #[cfg(unix)]
+        {
+            // SIGKILL を送信
+            unsafe {
+                libc::kill(child_id as i32, libc::SIGKILL);
+            }
+        }

あるいは、nix クレートを使用するか、この実行ファイルが Windows 専用であることをドキュメント化してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/check-ci-coderabbit/src/main.rs around lines 77 - 93, The timeout
handler currently only kills the child on Windows; update the thread closure
where flag_clone.store(...) is called (the std::thread::spawn block using
done_clone, flag_clone and child_id) to also terminate the child on Unix by
adding a cfg(unix) branch that sends SIGKILL (e.g., invoke "kill -9 <pid>" via
Command or call nix::sys::signal::kill or libc::kill) after setting the timeout
flag, ensuring the gh child process is terminated so wait_with_output doesn't
block; alternatively document Windows-only support if you choose not to
implement Unix handling.

Comment on lines +185 to +204
fn run_gh_quiet(args: &[&str]) -> Option<String> {
let output = Command::new("cmd")
.args(["/c", "gh"])
.args(args)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.output()
.ok()?;

if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
if s.is_empty() {
None
} else {
Some(s)
}
} else {
None
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

run_gh_quiet が Windows 専用になっている

Command::new("cmd").args(["/c", "gh"]) は Windows 固有の実装です。Linux/macOS では cmd コマンドが存在しないため失敗します。

🔧 クロスプラットフォーム対応の修正案
 fn run_gh_quiet(args: &[&str]) -> Option<String> {
-    let output = Command::new("cmd")
-        .args(["/c", "gh"])
+    let output = Command::new("gh")
         .args(args)
         .stdout(std::process::Stdio::piped())
         .stderr(std::process::Stdio::null())
         .output()
         .ok()?;

check-ci-coderabbit/src/main.rsrun_ghCommand::new("gh") を直接使用しており、そちらと一貫性を持たせてください。

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn run_gh_quiet(args: &[&str]) -> Option<String> {
let output = Command::new("cmd")
.args(["/c", "gh"])
.args(args)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.output()
.ok()?;
if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
if s.is_empty() {
None
} else {
Some(s)
}
} else {
None
}
}
fn run_gh_quiet(args: &[&str]) -> Option<String> {
let output = Command::new("gh")
.args(args)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.output()
.ok()?;
if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
if s.is_empty() {
None
} else {
Some(s)
}
} else {
None
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/hooks-post-pr-monitor/src/main.rs around lines 185 - 204, The
current run_gh_quiet uses Windows-only Command::new("cmd").args(["/c", "gh"])
which breaks on Linux/macOS; change it to invoke gh directly like the run_gh in
check-ci-coderabbit by replacing the Windows wrapper with
Command::new("gh").args(args) while preserving the stdout/stderr piping,
output().ok()? handling, and the existing logic that returns
Some(trimmed_string) on success or None on failure.

Comment on lines +283 to +295
/// デバッグログをファイルに追記 (テスト用 — 本番では削除)
fn debug_log(msg: &str) {
use std::io::Write;
let log_path = std::env::current_exe()
.unwrap_or_default()
.parent()
.unwrap_or(Path::new("."))
.join("post-pr-monitor-debug.log");
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&log_path) {
let _ = writeln!(f, "[{}] {}", utc_now_iso8601(), msg);
}
eprintln!("[post-pr-monitor] {}", msg);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

本番コードにデバッグ用関数が残っている

コメントに「テスト用 — 本番では削除」と明記されていますが、debug_log 関数とその呼び出し(lines 298, 303, 307, 313, 322, 327, 335, 346, 351)が残っています。

ファイル書き込みを伴うデバッグログは本番環境では不要であり、eprintln! での標準エラー出力のみで十分です。

🧹 デバッグコードの削除

debug_log 関数を削除し、呼び出し箇所を eprintln! に置き換えるか、完全に削除してください。

-/// デバッグログをファイルに追記 (テスト用 — 本番では削除)
-fn debug_log(msg: &str) {
-    use std::io::Write;
-    let log_path = std::env::current_exe()
-        .unwrap_or_default()
-        .parent()
-        .unwrap_or(Path::new("."))
-        .join("post-pr-monitor-debug.log");
-    if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&log_path) {
-        let _ = writeln!(f, "[{}] {}", utc_now_iso8601(), msg);
-    }
-    eprintln!("[post-pr-monitor] {}", msg);
-}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// デバッグログをファイルに追記 (テスト用 — 本番では削除)
fn debug_log(msg: &str) {
use std::io::Write;
let log_path = std::env::current_exe()
.unwrap_or_default()
.parent()
.unwrap_or(Path::new("."))
.join("post-pr-monitor-debug.log");
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&log_path) {
let _ = writeln!(f, "[{}] {}", utc_now_iso8601(), msg);
}
eprintln!("[post-pr-monitor] {}", msg);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/hooks-post-pr-monitor/src/main.rs around lines 283 - 295, Remove the
test-only debug_log function and replace its usages with eprintln! (or remove
the calls if redundant): delete the debug_log function definition and update all
places that call debug_log(...) to either call eprintln!("[post-pr-monitor] {}",
... ) with the same message expression or remove those calls entirely so no file
writes occur in production; search for debug_log invocations to update each call
site accordingly.

@aloekun
aloekun merged commit ee998c8 into master Apr 2, 2026
1 check passed
@aloekun
aloekun deleted the feat/post-pr-monitor branch April 2, 2026 12:53
aloekun added a commit that referenced this pull request Aug 6, 2026
…DR-071) (#361)

* feat(autonomy-policy): 未マージ draft 数の背圧を判定コアの入力にする

WP-18 PR 1 (1/5)。ADR-052 原則 5 が自動実行可クラスの前提条件とする背圧を、
判定コア `lib-autonomy-policy` が入力として受け取れるようにする。

## 変更の要点

`Operation::backpressure_connected()` の `DraftPr => false` 固定を廃し、背圧の
**指標の要求**と**指標の実測値**を別の層へ分けた:

- `Operation::requires_draft_backpressure()` — 操作クラスが「未マージ draft 数」を
  要求するかだけを持つ静的分類。状態は持たない
- `GateInputs::open_draft_prs` / `max_open_draft_prs` — 実測値と閾値。どちらか一方でも
  `None` なら `backpressure-unavailable` で deny (fail-closed)

背圧の状態を enum 側にも持たせると `GateInputs` と二重管理になり判定経路が分岐するため、
状態の保持先は `GateInputs` 1 箇所に限定した (計画書 WP-18 PR 1 の明示要件)。

`FixPush` が draft 数を要求しないのは背圧が無いからではなく、その背圧が cli-pr-monitor の
有界 retry (`max_retries`) で、呼び出しごとに gate へ渡す状態を持たないため。この非対称を
doc コメントとテスト (`fix_push_is_unaffected_by_draft_backpressure_inputs`) の両方で固定した。

## 追加した deny 理由

`DenyReason::BackpressureSaturated { open, limit }` (code = `backpressure-saturated`)。
飽和判定は `>=`。閾値は「これ以上は積まない」上限であり、`limit = 0` は「draft を 1 件も
作らない」= 実質停止を意味する。

`describe_sources` の背圧表記は `structural` / `ok(N/M)` / `saturated(N/M)` / `unavailable`
の 4 状態で、実数を必ず併記する。run log 1 行で「数え損ねて止まった」と「積み過ぎて
止まった」を切り分けられるようにするため。

## テスト

判定コアのテストを 16 → 22 件へ。網羅走査テストは背圧 4 パターンを軸に加えて 54 → 216
組合せになり、許可される組合せ数も式で固定した (truthy 表記数 × (fix-push 4 + draft-pr 1))。
境界 (`open == limit` で止まる)、`limit = 0`、kill-switch が背圧より先に効くこと、
背圧入力の欠落 3 パターンをそれぞれ独立に pin している。

## 本コミットの範囲

呼び手 2 件 (`cli-autonomy-gate` / `cli-fix-push-gate`) は本コミットでは `None` を渡す。
`draft-pr` は従来どおり deny のままで、運用挙動は変わらない。config からの閾値読み取りは
2/5、実測 draft 数の受け口は 3/5 で接続する。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(autonomy-policy): 背圧閾値 max_open_draft_prs を config から 1 回の read で取り込む

WP-18 PR 1 (2/5)。背圧の閾値を `autonomy-config.toml` の `[autonomy]
max_open_draft_prs` に置き、kill-switch フラグと同じ信頼境界 (master ref の写し) と
同じ「欠損 → 停止」極性に乗せる。

## read を 1 回に集約した理由

`read_repo_config_enabled(path) -> Option<bool>` を `read_repo_config(path) -> RepoConfig`
へ置き換えた。キーごとに読み取り関数を生やすとファイルを 2 回読むことになり、2 回の read の
間に config が差し替わると「kill-switch は旧世代・閾値は新世代」という混成状態で判定しうる。
1 回の read から派生した値だけを使う。

## 半壊 config は全フィールドが停止側へ倒れる

toml の parse はファイル単位なので、`max_open_draft_prs` が型違い (文字列 / 負値 / 小数) だと
`enabled` も含めて全フィールドが `None` になる。これは意図した挙動で、config の一部が壊れた
状態を「kill-switch だけ有効」で運転させない (ADR-043 fail-closed)。テストで明示的に固定した。

閾値キー欠落時に既定値 (3 など) へ倒さないのも同じ理由。書き忘れが「勝手に 3 件まで作る」
という fail-open にならないよう、欠落は背圧未接続 = draft PR deny とする。

一方で未知キーは無視する (`unknown_keys_are_ignored`)。config に新しい設定を足したときに、
未更新のバイナリが parse 失敗で全停止するのは安全側に振りすぎるため。

## テスト

sources のテストを 6 → 11 件へ。閾値の正常読み取り (0 を含む)、キー欠落が既定値でないこと、
型違い 3 種が kill-switch フラグごと停止側へ倒すこと、未知キー無視を追加した。

## 本コミットの範囲

`cli-autonomy-gate` は閾値を `GateInputs` へ渡すようになったが、実測 draft 数の受け口
(`--open-draft-prs`) はまだ無いため `draft-pr` は `backpressure-unavailable` で deny のまま。
接続は 3/5 で行う。`cli-fix-push-gate` は `enabled` しか使わないため呼び出し形の追従のみ。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(autonomy-gate): --open-draft-prs で実測 draft 数を受け取り背圧を接続する

WP-18 PR 1 (3/5)。判定コア (1/5) と閾値 (2/5) に続き、背圧の残り 1 入力である
**実測件数**の受け口を CLI に開ける。これで `draft-pr` が構造的 deny を脱し、
ADR-052 原則 5 の契約を満たした状態でのみ許可されるようになる。

## 引数の扱い

`--open-draft-prs <count>` は省略可能。`fix-push` では判定に使わないためで、`draft-pr` で
省略した場合は `None` = 背圧未接続として deny に倒れる。**省略が許可へ倒れる経路は無い**。

値のパースは `u32` で、空文字 / 負値 / 小数 / 非数値 / 末尾空白は引数不正 (exit 2) として
弾く。呼び手の `gh api` が失敗したときの出力 (空文字など) を 0 件と読み違えて「draft が
1 件も無いので作ってよい」に倒れるのが最悪の failure mode なので、ここは黙って `None` へ
潰さず loud に落とす。

`--config` / `--operation` の必須性は従来どおり。引数ループは値取得を各 arm へ寄せ、
未知フラグの判定を値の有無より先に行う既存の順序を保った。

## テスト

引数解析のテストを 4 → 7 件へ。省略時が `None` であること、`0` が正しく `Some(0)` として
読まれること (「0 件」と「数えられなかった」を型で区別する契約)、不正値 5 種が引数不正で
あることを固定した。

## 本コミットの範囲

exe 単体では背圧が接続された。実測値を渡す呼び手 (夜間 workflow の `gh api` step) は
**本 PR には含まれず PR 3 で追加する** (ADR-069 chain 宣言 → 計画書 § WP-18 と ADR-071 を
5/5 で更新)。実 exe による drill は 4/5。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(adr-071): 未マージ draft PR 数による背圧を起票し drill 12 シナリオを記録

WP-18 PR 1 (4/5)。1/5〜3/5 で実装した背圧の設計判断・試験運用条件・実測を永続化する。

## 記録した決定 5 件

1. 指標は「未マージ draft PR 数 (claude/ prefix)」。run 回数や経過時間のような代理指標
   ではなく、止めたい事象 (人間が捌けない量の未処理成果物) を直接数える
2. 背圧の状態は GateInputs だけが持つ。enum 側にも状態を置くと判定経路が二股に分かれ、
   「テストは通るが実運用では別の枝を通る」drift を生む
3. 閾値は autonomy-config.toml の max_open_draft_prs、判定は >=。0 は draft-pr クラス
   だけの停止、キー欠落は既定値ではなく deny
4. 数えるのは呼び手 (workflow step の gh api)、判断するのは gate。exe が gh に依存すると
   ローカル drill が GitHub 到達性に依存し、安全装置の再現可能な検証ができなくなる
5. 数えられなかったことは 0 件ではない。不正値は exit 2 で loud に落とす

決定 4 の「呼び手が数を偽れる」問題は、schedule イベントが default branch の workflow 定義を
使うという GitHub の仕様で担保される (ADR-066 の config master ref 契約と同じ信頼境界)。

## 外部 SaaS の課金・上限事実を移管 (計画書 § 2 の退役条件 2)

2026-08-06 に最新値を再確認して永続化した。

- public リポジトリ + standard runner の Actions 実行は無料・分数無制限 (GitHub 公式 docs で
  原文確認)。GitHub Free の 2,000 分/月は private のみ
- claude-code-action@v1 は claude_code_oauth_token で Max 枠を消費。OAuth token は個人
  サブスクに紐づき、自動化の消費が対話作業のレート枠を圧迫しうる

要約: Actions の実行時間は無料だが Max 枠は有限。背圧の経済的根拠はここにある。

## 検証記録

release build の実 exe で drill 12 シナリオを実施し全て設計どおりを確認した。#6/#7 の対で
>= 境界、#8 で limit=0 の停止、#9 で fix push が draft 数から独立していること、#10 で
半壊 config が enabled ごと停止することを実バイナリ上で確認している。

unit test は判定コア 22 / sources 11 / 引数解析 7 件。網羅走査 1 件が 216 組合せを走査する。

## bounded lifetime

decision trigger (b)「閾値到達で実際に次の run が止まること」は本 ADR 固有の観測点で、
exe 単体 drill では作れない状態 (夜間ループが実際に 3 件積む必要がある)。期限は 2026-11-06。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(harness-plan): WP-18 PR 1 の完了を記帳し PR 3 への chain 宣言と SaaS 事実の移管を行う

WP-18 PR 1 (5/5)。計画書側の 3 種類の更新。

## 1. PR 1 完了の記帳

- WP-18 節の見出しと全体像表を「着手可」→「着手中(PR 1 実装済み)」へ
- PR 構成 1 に実装時の設計判断を確定記録: 閾値判定の層は (a) gate 内を採用。
  backpressure_connected() は廃し requires_draft_backpressure()(指標の要求のみ)と
  GateInputs の 2 フィールド(状態)へ分離した
- 着手前決定 2 の記述を過去形へ(backpressure_connected() は既に存在しない名前のため、
  現在形のまま残すと ADR-069 決定 1 の「名前一致」要件に反する)
- WP-19 ステップ 2 を「前倒し済み」→「land 済み」へ

## 2. PR chain 宣言(ADR-069 決定 1)

PR 1 が導入した 3 点(--open-draft-prs フラグ / max_open_draft_prs キー / DraftPr の許可
経路)は PR 3 まで呼び手を持たない。抽出↔呼び手のペアリングを表で具体名指定した。

順序を逆にできない根拠も明記した — ADR-052 原則 5 が背圧の接続を draft-pr クラス有効化の
前提条件としているため、背圧が先に land する必要がある(WP-17 の kill-switch 先行と同構造)。

なお PR 1 単体では draft-pr は deny のままで運用挙動は変わらない。

## 3. SaaS 課金・上限事実の移管(退役条件 2 / 順位 117 の 3 ステップ原則)

permanent 側(ADR-071 § 外部 SaaS の課金・上限事実)を先に作成 → 本ファイルの § 2 から
GitHub Actions 課金 2 点と claude-code-action の OAuth 認証を削除し ADR-071 への参照へ
置き換えた。cloud routines の事実群は WP-19 / ADR-070 の担当範囲のため本節に残している。
runner 単価の相対比も private 化時のみ関係するため移管対象外とした。

pnpm lint:docs / markdownlint ともに green。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: CodeRabbit 指摘 2 件に対応 (主張の適用範囲を限定 / 移管済み方針の更新)

PR #361 への CodeRabbit レビュー (Minor 2 件) の反映。どちらも「書いた内容が実態より
広い/古い」型の指摘で、妥当。

## 1. 「draft-pr は deny のまま」の適用範囲を限定 (2 箇所)

指摘は「**両文書**が PR 3 まで draft-pr に許可経路が無いように読める」というもの。実際には
ADR-071 § 検証記録の drill #6 が示すとおり、--open-draft-prs と有効な config を渡せば exe は
allow になる。

正確には**リポジトリ内の自動化経路に --open-draft-prs を渡す呼び手が 1 つも無いため** deny に
なる、が正しい。「運用挙動は変わらない」の根拠を exe の挙動ではなく呼び手の不在へ置き直した。
根拠を取り違えたまま PR 3 で呼び手が入ると、主張だけが stale に残る。

修正箇所は 2 つ:

- docs/adr/adr-071-*.md § 残課題
- docs/harness-improvement-plan.md § WP-18 の PR chain 宣言

計画書側から ADR-071 § 残課題への参照も付け、同じ限定が 1 箇所に集約されるようにした。

## 2. 計画書 § 2 preamble: 移管済みの事実と矛盾する旧方針を更新

旧文は「現時点では ADR 化せず本ファイルに保持する」と書いていたが、同じ blockquote の
次の行で「ADR-071 へ移管済み」と述べており矛盾していた。

方針文を「担当 WP の ADR 起票時に移管し本節から削除する」という恒常ルールへ書き換え、
移管済み / 未移管を別項目に分けた。未移管の内訳 (cloud routines の daily cap / webhook
上限 / GitHub App 必須 / 緑ステータスの意味) も具体名で列挙し、次の担当 WP が何を移管
すべきか読み取れるようにした。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 8, 2026
#366)

* feat(nightly-todo): 改ざん検知を red 化し決定 10 の色分けと決定 6 の列挙基準を明文化する (ADR-072)

#363 の最終 push が pre-push security review の REJECT で止まったため master に
載らなかった 3 点を、master 上に載せ直す。いずれも可観測性と文書の改善で、
fail-closed 自体は master 版でも成立している。

## 改ざん検知を green から red へ (workflow)

ゲート資産の改ざん検知 step から continue-on-error を除去する。

fail-closed は元から成立していた — continue-on-error: true でも下流の
`if: steps.integrity.outcome == 'success'` で push は止まる。問題は**色**で、
green で終わるため run 一覧上「何もすることが無かった夜」と区別が付かなかった。
毎晩回る無人ループでは、この 2 つが混ざった時点で run 一覧が読まれなくなる。

改ざん検知は「何かがゲートを無効化しようとした」という、この系が出しうる最も
大きい信号である。red で落ちても後続 step は if: の評価前に skip されるため
push には到達せず、Report outcome は if: '!cancelled()' なので診断行は出る。

## 決定 10 に色分け表を追加 (ADR-072)

「設計された停止」と「インフラ障害」の 2 分類を表にしたところ、**改ざん検知が
どちらにも入っていない**ことが露出した。分類を明文化すると分類に入らない結末が
可視になる、の実例として § 静的レビューが捕捉した件 の #10 に記録した。
見落としていたのは安全性ではなく可観測性の側だった。

## 決定 6 に列挙基準を追加 (ADR-072)

禁止リストの基準は「危険か」ではなく「**将来の無人 run のゲートを緩めるか**」で
ある。security review が挙げた Cargo.toml / Cargo.lock の欠落を採らない根拠が
これで、通常の diff は人間の PR レビューとマージという既存の防衛線が効く。
基準を持たないと禁止リストは「怪しいもの全部」へ膨らみ正当なタスクを弾き始める。

## 適用方法

保持していたローカル bookmark (wp18/unpushed-improvements) は #363 マージ前の
スタックのため、そのまま復元すると **#364 で入れた ADR-072 § 残課題 の追記
(外部設定の実体が未記録 / 秘密値は記録しない) を巻き戻す**。したがって workflow は
ファイル単位で restore し、ADR-072 は追加分 4 箇所のみ手で適用した。適用後に
lpzvttwu との差分が #364 の 1 行だけであることを実測確認している。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(nightly-todo): 決定 10 の red 分類を残り 3 step へ適用する (#366 CodeRabbit 指摘)

自動 fix 経路が作った commit を、内容を実測検証したうえで採用したもの。
**push 自体は scope guard (ADR-054) が BLOCK した** — finding の anchor が
docs/adr/adr-072 なのに fix が .github/workflows/ を触ったため。指摘の remedy が
anchor と別ファイルにある典型で、guard の設計どおりの挙動だが本件は injection では
ないため誤検知にあたる (WP-11 の enforce 期間の観測データとして計上すべき)。

## 指摘の妥当性

同 PR で追加した決定 10 の色分け表は「gh / network / clone の失敗 → red」と
定めているのに、その 3 経路が continue-on-error: true で green に落ちていた。
表を追加した PR 自身が作った不整合であり、妥当と判断して採用する。

- Prepare a clean publish tree — git clone (ネットワーク I/O)
- Mint App token — GitHub API 呼び出し (secret 誤設定・GitHub 障害)
- Push branch and open draft PR — git push / gh pr create

## 実測検証 (fix の出力を鵜呑みにしない)

- 下流の if: はいずれも `steps.<id>.outcome == 'success'` 形式のため、失敗時は
  後続が skip され push へ到達しない (fail-closed は維持)
- Report outcome は `if: '!cancelled()'` なので red でも診断行は出る
- dry_run=true では app-token / publish は if: により **skipped** (failed ではない)
  ため、dry_run の run は green のまま

## ADR への記帳

表を書いた著者自身は 1 件 (改ざん検知) しか見つけられず、残り 3 件は他者の
レビューで出た。**分類の明文化は露出の必要条件であって十分条件ではない**ことの
実例として決定 10 へ追記した。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant