Skip to content

feat(hooks): add post-push CI monitoring and enable PR AI review - #385

Merged
keito4 merged 1 commit into
mainfrom
feat/add-post-push-ci-hook
Jan 29, 2026
Merged

feat(hooks): add post-push CI monitoring and enable PR AI review#385
keito4 merged 1 commit into
mainfrom
feat/add-post-push-ci-hook

Conversation

@keito4

@keito4 keito4 commented Jan 29, 2026

Copy link
Copy Markdown
Owner

概要

git push後のCI監視とPR作成後のAIレビューを自動化するHooksを追加しました。

追加されたHooks

1. post_git_push_ci.py (新規)

トリガー: git push 成功後

機能:

  • GitHub Actions ワークフローの起動を検出
  • 最大5分間CIの実行を監視
  • 成功/失敗の結果をリアルタイムで報告

出力例:

==============================================================
🚀 Push完了。GitHub Actions CIを確認中...
==============================================================

📋 ワークフロー: CI
   Run ID: 12345678
   Status: in_progress

🔄 CI実行を監視中... (最大5分)
   ⏳ 15秒経過... (status: in_progress)
   ⏳ 30秒経過... (status: in_progress)

✅ CI成功!
==============================================================

2. post_pr_ai_review.py (有効化)

トリガー: gh pr create 成功後

機能:

  • Codex CLI または Gemini CLI でコードレビューを実行
  • 正確性、パフォーマンス、セキュリティ、保守性を評価
  • verdict("patch is correct" / "patch is incorrect")と信頼度スコアを出力

変更ファイル

ファイル 変更内容
.claude/hooks/post_git_push_ci.py 新規作成 - CI監視Hook
.claude/hooks/README.md ドキュメント追加
.devcontainer/claude-settings.local.json PostToolUse設定追加

設定方法

settings.local.json に以下を追加:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "tool_name == 'Bash'",
        "hooks": [
          { "type": "command", "command": "python3 .claude/hooks/post_git_push_ci.py" }
        ]
      },
      {
        "matcher": "tool_name == 'Bash'",
        "hooks": [
          { "type": "command", "command": "python3 .claude/hooks/post_pr_ai_review.py" }
        ]
      }
    ]
  }
}

テスト

  • ✅ pre-commit フック通過(Format, Lint, Test)
  • ✅ Python構文チェック済み

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added automatic GitHub Actions CI monitoring after git push operations. The system watches workflow runs for up to 5 minutes and reports outcomes (success, failure, or timeout) without blocking the push flow.
  • Documentation

    • Updated hook documentation to describe the new post-git-push CI monitoring functionality.

✏️ Tip: You can customize this high-level summary in your review settings.

## 追加されたHooks

### PostToolUse
- `post_git_push_ci.py`: git push後にGitHub Actions CIを自動監視
  - 最大5分間CIの実行を監視
  - 成功/失敗の結果を報告
  - ブロックはしない(情報提供のみ)

- `post_pr_ai_review.py`: PR作成後のCodex/Geminiレビュー(有効化)
  - Codex CLIまたはGemini CLIでコードレビューを実行
  - 正確性、パフォーマンス、セキュリティ、保守性を評価

## 設定ファイル更新
- `.devcontainer/claude-settings.local.json`: DevContainer用PostToolUse設定追加
- `.claude/hooks/README.md`: 新hookのドキュメント追加

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the size/M PR サイズ Medium label Jan 29, 2026
@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Introduces a new GitHub Actions CI monitoring hook that observes git push operations, validates successful completion, queries the current Git branch, fetches the latest workflow run via gh CLI, and polls its status for up to 5 minutes with periodic progress reporting.

Changes

Cohort / File(s) Summary
Documentation Update
.claude/hooks/README.md
Adds documentation for the new post_git_push_ci.py hook, including purpose, trigger behavior, prerequisites, and configuration examples; renumbers subsequent sections accordingly.
New CI Monitoring Hook
.claude/hooks/post_git_push_ci.py
New Python script that monitors GitHub Actions CI after git push by validating Bash commands, detecting push success/failure patterns, querying current Git branch, fetching latest workflow run status via gh CLI, polling every 15 seconds for up to 5 minutes, and reporting results to stderr.
Hook Configuration
.devcontainer/claude-settings.local.json
Registers the new post_git_push_ci.py hook as a PostToolUse entry for Bash commands in Claude settings.

Sequence Diagram

sequenceDiagram
    actor User
    participant Claude
    participant Bash as Bash Tool
    participant Hook as post_git_push_ci.py
    participant Git as Git
    participant GitHub as GitHub API<br/>(via gh CLI)

    User->>Claude: Request git push operation
    Claude->>Bash: Execute git push command
    Bash->>GitHub: Push code
    Bash-->>Hook: Post-action trigger
    Hook->>Bash: Validate command (is git push?)
    Bash-->>Hook: Command confirmed
    Hook->>Git: Query current branch
    Git-->>Hook: Return branch name
    Hook->>GitHub: Fetch latest workflow run
    GitHub-->>Hook: Return workflow run
    
    rect rgba(173, 216, 230, 0.5)
        Note over Hook: Polling Loop<br/>(max 5 min, every 15 sec)
        Hook->>GitHub: Check workflow status
        GitHub-->>Hook: Return status
        
        alt Status: Success
            Hook-->>User: Report success
        else Status: Failure
            Hook-->>User: Report failure + details
        else Status: Still Running
            Hook->>Hook: Wait & retry
        end
    end
    
    Hook-->>User: Exit with code 0 (non-blocking)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

size/XS

Poem

🐰 A hook upon the push so bright,
Watches CI workflows through the night,
Polling, polling, every beat—
Five minutes till our job's complete! ✨

🚥 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 title accurately describes the main additions: a new post-push CI monitoring hook and enabling PR AI review, which are the primary objectives of this changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.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

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.

@claude

claude Bot commented Jan 29, 2026

Copy link
Copy Markdown
Contributor

PR レビュー: Post-Push CI Monitoring Hook

このPRを確認しました。git push後のCI監視を自動化する機能追加です。全体的に良い実装ですが、いくつかの改善提案があります。

✅ 良い点

  1. 明確な目的と実装: CI監視の自動化により開発体験が向上します
  2. 適切なエラーハンドリング: タイムアウトやエラー時の処理が実装されています
  3. 非ブロッキング設計: PostToolUseフックとして適切に実装されており、常に sys.exit(0) で終了します
  4. 詳細なドキュメント: README.mdに使用方法と動作が明記されています

🔍 コード品質に関する指摘

1. 潜在的な競合状態 (.claude/hooks/post_git_push_ci.py:93)

固定の3秒待機では、ワークフロー起動が遅い場合に前回のpushのCIを取得する可能性があります。

提案: headSha を使用してpush後のcommit SHAと照合する

2. returnコード検証の不足 (.claude/hooks/post_git_push_ci.py:80)

git rev-parse が失敗した場合でも stdout をそのまま返します。

提案: returncode を確認してから結果を返す

3. 例外ハンドリングが広すぎる (.claude/hooks/post_git_push_ci.py:110-112)

すべての例外を捕捉すると、予期しないエラーの詳細が失われます。

提案: 特定の例外のみを捕捉するか、エラーの種類を明示する

⚡ パフォーマンスに関する指摘

4. 最大5分の同期待機 (.claude/hooks/post_git_push_ci.py:115-150)

watch_ci_run() が最大300秒(5分)ブロックします。これはPostToolUseフックとして長すぎる可能性があります。

提案: タイムアウトを短縮するか、環境変数で設定可能にする

🔒 セキュリティに関する指摘

5. コマンドインジェクションのリスク低減

✅ すべての subprocess.run() 呼び出しでリスト形式のコマンド引数を使用しており、セキュリティ面は良好です。

🧪 テストカバレッジに関する指摘

6. ユニットテストが存在しない

このフックスクリプトに対するテストが含まれていません。CLAUDE.mdの要件(70%+カバレッジ)を満たしていません。

提案: 以下のテストケースを追加

  • Bashツール以外はスキップする
  • git push以外のコマンドはスキップする
  • pushエラー時はスキップする
  • ワークフローが見つからない場合の処理
  • CI監視タイムアウトの処理
  • 正しいSHAのrunを取得する

📝 ドキュメントに関する指摘

7. README.mdの設定例の不一致

実際の設定ファイルのパスが /home/vscode/.claude/hooks/post_git_push_ci.py なのに対し、READMEの例では相対パスになっています。

📊 総合評価

カテゴリ 評価 コメント
コード品質 ⚠️ 改善の余地あり(競合状態、エラーハンドリング)
パフォーマンス ⚠️ 5分の待機は長い可能性
セキュリティ ✅ 良 コマンドインジェクション対策済み
テストカバレッジ ❌ 不足 ユニットテストが必要
ドキュメント ⚠️ 設定例の不一致を修正

✅ マージ可否

以下のクリティカルな問題を修正後、マージを推奨します:

  1. 必須: ユニットテストの追加(最低限の品質基準)
  2. 推奨: SHA照合による正確なrun検出
  3. 推奨: エラーハンドリングの改善

テスト追加なしでのマージはCLAUDE.mdの品質基準(70%+カバレッジ)に抵触します。


機能自体は有用で、実装も概ね良好です。テストを追加すれば品質基準を満たせます。

@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: 1

🤖 Fix all issues with AI agents
In @.claude/hooks/post_git_push_ci.py:
- Around line 85-109: get_latest_run may return a stale workflow because it
queries the latest run once; change it to retry for up to 30 seconds and return
the run whose headSha equals the current HEAD SHA. Specifically, inside
get_latest_run (which already calls get_current_branch and parses gh run list
output), loop with a short sleep (e.g., 1–3s) until a timeout, call gh run list
(increase --limit to fetch several recent runs) each iteration, parse runs and
search for a run where run["headSha"] == current_head_sha (obtain
current_head_sha via git rev-parse HEAD), and return that matched run; if
timeout elapses return None. Ensure subprocess.run error handling and timeouts
remain intact and keep existing return shapes.

Comment on lines +85 to +109
def get_latest_run():
"""最新のworkflow runを取得"""
try:
branch = get_current_branch()
if not branch:
return None

# 少し待ってからCIの状態を確認(ワークフロー起動に時間がかかる場合があるため)
time.sleep(3)

result = subprocess.run(
["gh", "run", "list", "--branch", branch, "--limit", "1", "--json", "databaseId,status,conclusion,name,workflowName,headSha,createdAt"],
capture_output=True,
text=True,
timeout=30
)

if result.returncode != 0:
return None

runs = json.loads(result.stdout)
if runs:
return runs[0]
return 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

🧩 Analysis chain

🌐 Web query:

GitHub CLI gh run list --json fields headSha documentation

💡 Result:

gh run list supports JSON output via --json <fields>. One of the valid fields is headSha (the run’s commit SHA). (cli.github.com)

Docs / reference

Example

gh run list --json headSha

Citations:


🌐 Web query:

GitHub CLI gh run list default ordering newest first chronological

💡 Result:

gh run list shows recent workflow runs and (in practice) the output is newest-first (reverse chronological). The command has no --sort/--order flags, so you can’t switch it to oldest-first directly. [1]

If you need chronological (oldest-first) ordering, fetch JSON and sort client-side, e.g.:

gh run list --limit 100 --json databaseId,createdAt,displayTitle,status,conclusion \
  --jq 'sort_by(.createdAt)[]'

Notes:

  • GitHub’s workflow-runs REST API does not clearly guarantee the default ordering in the docs; it’s commonly observed as created_at descending (newest first), but if you need a hard guarantee you must sort yourself. [2][3]

Sources: GitHub CLI manual for gh run list [1]; GitHub REST API workflow-runs docs (ordering not explicitly guaranteed) [2]; GitHub Community discussion on observed default order [3].


🏁 Script executed:

cat -n .claude/hooks/post_git_push_ci.py | head -120

Repository: keito4/config

Length of output: 3700


Match CI runs to the pushed commit to avoid stale status.

gh run list --limit 1 right after a push can return the previous run if the new run hasn't started yet, which can misreport CI status. The current code already fetches headSha but doesn't use it for matching. Replace the single-query approach with a retry loop that matches runs by the current HEAD SHA over a 30-second window.

🛠️ Suggested fix (match run by HEAD SHA with a short retry window)
+def get_current_head_sha():
+    """現在のHEAD SHAを取得"""
+    try:
+        result = subprocess.run(
+            ["git", "rev-parse", "HEAD"],
+            capture_output=True,
+            text=True,
+            timeout=10
+        )
+        if result.returncode != 0:
+            return None
+        return result.stdout.strip()
+    except Exception:
+        return None
+
 def get_latest_run():
     """最新のworkflow runを取得"""
     try:
         branch = get_current_branch()
-        if not branch:
+        head_sha = get_current_head_sha()
+        if not branch or not head_sha:
             return None
 
-        # 少し待ってからCIの状態を確認(ワークフロー起動に時間がかかる場合があるため)
-        time.sleep(3)
-
-        result = subprocess.run(
-            ["gh", "run", "list", "--branch", branch, "--limit", "1", "--json", "databaseId,status,conclusion,name,workflowName,headSha,createdAt"],
-            capture_output=True,
-            text=True,
-            timeout=30
-        )
-
-        if result.returncode != 0:
-            return None
-
-        runs = json.loads(result.stdout)
-        if runs:
-            return runs[0]
-        return None
+        deadline = time.time() + 30
+        while time.time() < deadline:
+            time.sleep(3)
+            result = subprocess.run(
+                ["gh", "run", "list", "--branch", branch, "--limit", "20",
+                 "--json", "databaseId,status,conclusion,name,workflowName,headSha,createdAt"],
+                capture_output=True,
+                text=True,
+                timeout=30
+            )
+            if result.returncode != 0:
+                return None
+            runs = json.loads(result.stdout)
+            for run in runs:
+                if run.get("headSha") == head_sha:
+                    return run
+        return None
📝 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
def get_latest_run():
"""最新のworkflow runを取得"""
try:
branch = get_current_branch()
if not branch:
return None
# 少し待ってからCIの状態を確認(ワークフロー起動に時間がかかる場合があるため)
time.sleep(3)
result = subprocess.run(
["gh", "run", "list", "--branch", branch, "--limit", "1", "--json", "databaseId,status,conclusion,name,workflowName,headSha,createdAt"],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
return None
runs = json.loads(result.stdout)
if runs:
return runs[0]
return None
def get_current_head_sha():
"""現在のHEAD SHAを取得"""
try:
result = subprocess.run(
["git", "rev-parse", "HEAD"],
capture_output=True,
text=True,
timeout=10
)
if result.returncode != 0:
return None
return result.stdout.strip()
except Exception:
return None
def get_latest_run():
"""最新のworkflow runを取得"""
try:
branch = get_current_branch()
head_sha = get_current_head_sha()
if not branch or not head_sha:
return None
deadline = time.time() + 30
while time.time() < deadline:
time.sleep(3)
result = subprocess.run(
["gh", "run", "list", "--branch", branch, "--limit", "20",
"--json", "databaseId,status,conclusion,name,workflowName,headSha,createdAt"],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
return None
runs = json.loads(result.stdout)
for run in runs:
if run.get("headSha") == head_sha:
return run
return None
🧰 Tools
🪛 Ruff (0.14.14)

92-92: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


92-92: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)


95-95: subprocess call: check for execution of untrusted input

(S603)


96-96: Starting a process with a partial executable path

(S607)


108-108: Consider moving this statement to an else block

(TRY300)

🤖 Prompt for AI Agents
In @.claude/hooks/post_git_push_ci.py around lines 85 - 109, get_latest_run may
return a stale workflow because it queries the latest run once; change it to
retry for up to 30 seconds and return the run whose headSha equals the current
HEAD SHA. Specifically, inside get_latest_run (which already calls
get_current_branch and parses gh run list output), loop with a short sleep
(e.g., 1–3s) until a timeout, call gh run list (increase --limit to fetch
several recent runs) each iteration, parse runs and search for a run where
run["headSha"] == current_head_sha (obtain current_head_sha via git rev-parse
HEAD), and return that matched run; if timeout elapses return None. Ensure
subprocess.run error handling and timeouts remain intact and keep existing
return shapes.

@keito4
keito4 merged commit 42ad571 into main Jan 29, 2026
17 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.58.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released リリース済み size/M PR サイズ Medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant