Update: gitea webhook article with latest process and merge duplicated process article
All checks were successful
Deploy Docusaurus Site / deploy (push) Successful in 26s

This commit is contained in:
koide 2026-02-27 03:40:07 +00:00
parent f236ffd748
commit f9239be35b
4 changed files with 81 additions and 441 deletions

View File

@ -1,111 +0,0 @@
---
sidebar_position: 2
title: Gitea × OpenClaw 連携の開発プロセス実践ガイド
description: Issue/PR Webhook連携でAI開発を回すための実運用プロセスブランチ戦略、レビュー、マージ、再発防止
hide_table_of_contents: false
displayed_sidebar: null
---
# Gitea × OpenClaw 連携の開発プロセス実践ガイド
## はじめに
Gitea Webhook で OpenClaw を呼び出す構成は便利ですが、運用ルールが曖昧だとすぐ壊れます。
この記事では、実際に運用しながら整えた **Issue/PR 駆動の開発プロセス** をまとめます。
## 目的
- Issue を起点に AI が修正提案・実装を進める
- PR でレビューし、問題なければ main へ反映
- ブランチ運用・反映手順を毎回明示し、Git に不慣れでも迷わない
## 全体フロー
1. Gitea で Issue を作成
2. Webhook Bridge が OpenClaw にタスク転送
3. 修正ブランチで実装・検証
4. PR 作成
5. PR Webhook で最終レビュー
6. main へマージ
7. Issue に進捗コメント(手順付き)
## 運用ルール(重要)
### 1) 変更は必ず作業ブランチで
- `fix/issue-<番号>-<要約>`
- `feat/issue-<番号>-<要約>`
- `docs/issue-<番号>-<要約>`
### 2) Issue コメントに毎回この3点を記載
- ブランチ切替コマンド
- 検証コマンド
- main へのマージコマンド
### 3) PR イベントも Webhook で処理
`issues` だけでなく `pull_request` も受ける。
対象アクションは最低限以下を推奨:
- `opened`
- `reopened`
- `synchronize`
## 実用テンプレ(そのまま使える)
### ブランチ切替
```bash
git fetch origin
git switch fix/issue-123-xxx
# 古いgitなら: git checkout fix/issue-123-xxx
```
### 検証
```bash
python3 -m py_compile src/*.py app/*.py
python3 app/web_ui.py --port 7860 --config config/config.yaml
```
### main へ反映
```bash
git switch main
git pull origin main
git merge --no-ff fix/issue-123-xxx
git push origin main
```
## 破綻しやすいポイントと対策
### Webhook が `EOF` で失敗
- 原因の多くは受信サーバー側の例外落ち
- `do_POST` 全体を `try/except` で囲み、必ず HTTP 応答を返す
### 別機能の修正で Webhook が壊れる
- ブリッジ機能を分離(例: Gitea用 / Discord用を別リポ・別サービス
- systemd サービスも分離
### ローカル設定ファイルが pull を壊す
- `config.yaml.example` を追跡
- `config.yaml``.gitignore`
## まとめ
Gitea × OpenClaw 連携は、技術そのものより **運用ルールの明文化** が効きます。
- ブランチ命名を固定
- Issue コメントのテンプレを固定
- PR Webhook まで含める
- 失敗しやすい箇所を分離・自動化
この4つを守るだけで、連携はかなり安定します。
---
*この記事は2026年2月時点の情報です。*

View File

@ -1,7 +1,7 @@
---
sidebar_position: 2
title: ローカルGitea × Webhook連携で、AIとの開発をもっと楽にしよう
description: GiteaのWebhookとPythonブリッジを使って、Issue作成だけでAIが自動コードレビュー修正してくれる仕組みを作る
description: GiteaのIssue/PR WebhookとOpenClawを連携し、壊れにくい自動レビュー開発フローを構築する実践ガイド
hide_table_of_contents: false
displayed_sidebar: null
---
@ -10,284 +10,87 @@ displayed_sidebar: null
## はじめに
「Issue を立てるだけで、AI が自動でコードレビューして修正まで出してくれたらなぁ…」
Issueを作ったらAIがレビューして、PRが来たらAIが最終チェックして、最後はmainに安全に反映する。
そう思ったことはありませんか? 実はローカルの Gitea と Webhook、そして AI エージェントを繋げるだけで、わりと簡単にそんな世界が実現できます。
この記事は、Gitea + OpenClaw 連携を **実運用で壊れにくく回すための最新版プロセス** をまとめたものです。
この記事では、**Gitea で Issue が作成されたら自動で AI コードレビューが走る仕組み**を、Python の標準ライブラリだけで作った話を紹介します。
## できること
## 全体像
- Issue作成で AI レビュー/修正タスクを起動
- Pull Request 作成でも AI レビューを起動
- ブランチ運用・反映手順をテンプレ化して Git 不慣れでも回せる
- ブリッジを systemd 常駐して安定運用
構成はシンプルです。
## 最新アーキテクチャ
```
┌─────────┐ Webhook (HTTP POST) ┌──────────────┐
│ Gitea │ ──────────────────────────→ │ bridge.py
│ (別ホスト) │ │ (Python HTTP)
└─────────┘ └──────┬───────┘
┌─────────┐ Webhook (issues / pull_request) ┌────────────────────┐
│ Gitea │ ────────────────────────────────────→ │ gitea-webhook-bridge
└─────────┘ │ (bridge.py)
└──────────────────┘
openclaw agent コマンド
openclaw agent
┌──────▼───────┐
│ OpenClaw │
│ (AI Agent) │
└──────┬───────┘
code-review-loop
code-review-loop スキル
┌──────▼───────┐
│ レビュー結果を │
│ Issue にコメント │
└──────────────┘
Issueコメント
```
**流れ:**
## Webhook対象イベント
1. Gitea で Issue が作成される
2. システム Webhook が `bridge.py` に POST を送る
3. `bridge.py` が HMAC 署名を検証し、`openclaw agent` コマンドを実行
4. OpenClaw が `code-review-loop` スキルでリポジトリをレビュー&修正
5. 結果が Gitea の Issue にコメントとして返ってくる
現在の推奨設定は以下です。
## 前提
- `issues`:
- `opened`
- `pull_request`:
- `opened`
- `reopened`
- `synchronize`
この記事では以下が揃っている前提で進めます:
`pull_request` 未対応だと、PRで `OK (ignored event)` になって自動レビューされません。
- **ローカル Gitea** がセットアップ済み別ホストでもOK
- **OpenClaw**AI エージェントフレームワーク)がセットアップ済み
- bridge.py を動かすホストに Python 3 がある
## bridge.py の要点
:::tip
OpenClaw は AI エージェントを CLI やサービスとして動かせるフレームワークです。`openclaw agent --message "..."` で 1 回限りのタスクを実行できます。
- `X-Gitea-Signature` で HMAC 検証
- `/webhook/gitea``issues` / `pull_request` を分岐処理
- `openclaw agent` を非同期で起動
- Webhookハンドラで例外が起きても **必ずHTTPレスポンスを返す**EOF対策
:::warning
`EOF` が出る場合、受信サーバーが例外落ちしてレスポンス返せていないケースが多いです。
`do_POST` 全体を `try/except` で保護し、`500` を返すようにしてください。
:::
## bridge.py の実装
## 運用で効いたルール
ポイントは **Python 標準ライブラリのみ** で作っていること。`pip install` 不要です。
### 1) ブランチ命名を固定
```python title="bridge.py"
#!/usr/bin/env python3
"""Gitea Webhook Bridge - forwards new Issues to OpenClaw for auto code review."""
- `fix/issue-<番号>-<要約>`
- `feat/issue-<番号>-<要約>`
- `docs/issue-<番号>-<要約>`
import hashlib
import hmac
import json
import logging
import os
import subprocess
import sys
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
### 2) Issueコメントに毎回添える3点
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
stream=sys.stdout,
)
log = logging.getLogger("bridge")
- ブランチ切替コマンド
- 検証コマンド
- main へのマージコマンド
CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
### 3) 機能を分離する
Webhook橋渡しを1つに詰め込みすぎると壊れます。
def load_config():
with open(CONFIG_PATH) as f:
return json.load(f)
実運用では以下のように分離すると安全です。
- `gitea-webhook-bridge`Issue/PR連携専用
- `discord-notify-bridge`(通知専用)
def verify_signature(secret: str, body: bytes, signature: str) -> bool:
mac = hmac.new(secret.encode(), body, hashlib.sha256)
return hmac.compare_digest(mac.hexdigest(), signature)
サービスも別systemdに分離。
def run_agent(task: str, label: str):
"""Run openclaw agent with a message instructing it to spawn a sub-agent."""
import time
session_id = f"gitea-webhook-{label}-{int(time.time())}"
message = (
f"以下のタスクを sessions_spawn (mode=run) で実行してください。"
f"自分では処理せず、必ず sessions_spawn tool を使って"
f"サブエージェントに委譲してください。\n\n"
f"label: {label}\n\n"
f"task:\n{task}"
)
cmd = [
"openclaw", "agent",
"--session-id", session_id,
"--message", message,
"--timeout", "120",
]
log.info("Running openclaw agent for label=%s", label)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=180,
)
if result.returncode == 0:
log.info("Agent completed for %s: %s", label, result.stdout[:200])
else:
log.error(
"Agent failed for %s: rc=%d stderr=%s",
label, result.returncode, result.stderr[:200],
)
except subprocess.TimeoutExpired:
log.error("Agent timed out for %s", label)
except Exception as e:
log.error("Agent error for %s: %s", label, e)
class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/webhook/gitea":
self.send_response(404)
self.end_headers()
return
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length)
# Signature verification
config = load_config()
secret_env = config.get("webhook_secret_env", "GITEA_WEBHOOK_SECRET")
secret = os.environ.get(secret_env, "")
if secret:
sig = self.headers.get("X-Gitea-Signature", "")
if not verify_signature(secret, body, sig):
log.warning("Signature verification failed")
self.send_response(403)
self.end_headers()
self.wfile.write(b"Invalid signature")
return
# Only process issue events
event = self.headers.get("X-Gitea-Event", "")
if event != "issues":
log.info("Ignoring event: %s", event)
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK (ignored event)")
return
try:
payload = json.loads(body)
except json.JSONDecodeError:
self.send_response(400)
self.end_headers()
return
action = payload.get("action", "")
if action != "opened":
log.info("Ignoring action: %s", action)
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK (ignored action)")
return
# Extract info
issue = payload.get("issue", {})
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name", "")
clone_url = repo.get("clone_url", "")
number = issue.get("number", 0)
title = issue.get("title", "")
body_text = issue.get("body", "")
html_url = issue.get("html_url", "")
# Check ignore list
ignore_repos = config.get("ignore_repos", [])
if repo_full_name in ignore_repos:
log.info("Ignoring repo: %s", repo_full_name)
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK (ignored repo)")
return
# Build task
task = (
f"Gitea Issue #{number} on {repo_full_name}: {title}\n\n"
f"{body_text}\n\n"
f"リポジトリ: {clone_url}\n"
f"Issue URL: {html_url}\n\n"
f"code-review-loop スキルに従ってレビュー&修正してください。"
)
label = f"gitea-review-{repo_full_name.replace('/', '-')}-{number}"
log.info("Spawning agent for %s#%d: %s", repo_full_name, number, title)
# Run in background thread
threading.Thread(
target=run_agent,
args=(task, label),
daemon=True,
).start()
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK")
def do_GET(self):
if self.path == "/health":
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK")
return
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass
def main():
config = load_config()
bind = config.get("bind", "127.0.0.1")
port = config.get("port", 9876)
server = HTTPServer((bind, port), WebhookHandler)
log.info("Listening on %s:%d", bind, port)
try:
server.serve_forever()
except KeyboardInterrupt:
log.info("Shutting down")
server.server_close()
if __name__ == "__main__":
main()
```
### 設定ファイル
```json title="config.json"
{
"bind": "0.0.0.0",
"port": 9876,
"webhook_secret_env": "GITEA_WEBHOOK_SECRET",
"ignore_repos": []
}
```
### 環境変数
```bash title=".env"
GITEA_WEBHOOK_SECRET=ここにWebhookシークレットを設定
```
### コードのポイント
- **HMAC-SHA256 署名検証**: Gitea が送ってくる `X-Gitea-Signature` ヘッダーを検証。なりすまし防止
- **Issue の `opened` アクションのみ処理**: 編集やクローズでは発火しない
- **バックグラウンドスレッド**: Webhook のレスポンスを即座に返し、エージェント実行は非同期
- **`ignore_repos`**: レビュー不要なリポジトリを除外できる
- **`/health` エンドポイント**: 監視用のヘルスチェック
## systemd サービス化
常駐させるために systemd ユニットを作ります。
## systemd の基本
```ini title="/etc/systemd/system/gitea-webhook-bridge.service"
[Unit]
Description=Gitea Webhook Bridge
Description=Gitea Webhook Bridge for OpenClaw
After=network.target
[Service]
@ -309,102 +112,57 @@ sudo systemctl enable --now gitea-webhook-bridge
sudo systemctl status gitea-webhook-bridge
```
## Gitea 側の設定
## Gitea 側チェックポイント
### ALLOWED_HOST_LIST の設定
:::warning 重要
Gitea はデフォルトで **プライベート IP への Webhook 送信をブロック** します。ローカルネットワーク内で使う場合、この設定が必須です。
:::
Gitea の `app.ini` に以下を追加します:
### `ALLOWED_HOST_LIST`(必須)
```ini title="app.ini"
[webhook]
ALLOWED_HOST_LIST = private
```
`private` を指定すると、RFC 1918 のプライベートアドレス(`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`)への送信が許可されます。
### Webhook設定
設定後は Gitea の再起動が必要です。
- URL: `http://<bridge-host>:9876/webhook/gitea`
- Content-Type: `application/json`
- Secret: bridge側と一致
- トリガー: `issues`, `pull_request`
### システム Webhook の追加
## Git弱者向けテンプレそのまま使える
Gitea の **サイト管理 → Webhook** からシステム Webhook を追加します。
| 項目 | 設定値 |
|------|--------|
| ターゲット URL | `http://<bridge のホスト IP>:9876/webhook/gitea` |
| Content Type | `application/json` |
| Secret | `openssl rand -hex 20` で生成した値 |
| トリガー | Issues イベント |
システム Webhook にすると、**全リポジトリ**に対して自動的に適用されます。個別に設定する手間がなくて楽です。
## ハマりポイント 🤦
実際に構築してみて踏んだ地雷たちです。
### 1. ALLOWED_HOST_LIST を忘れて Webhook が飛ばない
Gitea の Webhook 配信テストで `Delivery: ...is not allowed` みたいなエラーが出たら、これが原因。`app.ini``[webhook]` セクションを追加し忘れていないか確認しましょう。
### 2. bind アドレスが 127.0.0.1 のまま
Gitea が別ホストにある場合、bridge.py が `127.0.0.1` で listen していると当然届きません。`config.json``bind``0.0.0.0` に変更しましょう。
### 3. `openclaw session spawn` は存在しない
OpenClaw の CLI コマンドを間違えがち。セッションを起動するには `openclaw agent --message "..."` を使います。
## 動作テスト
### 1. bridge.py の起動確認
### 作業ブランチへ切替
```bash
# ヘルスチェック
curl http://localhost:9876/health
# → OK
git fetch origin
git switch fix/issue-123-xxx
# 古いGit: git checkout fix/issue-123-xxx
```
### 2. Gitea から Webhook テスト
### 検証
Gitea のサイト管理 → Webhook → 作成した Webhook → **テスト配信** ボタンを押します。
bridge.py のログに以下のような出力が出ればOK
```
2026-02-25 12:00:00 [INFO] Ignoring event: push
```bash
python3 -m py_compile src/*.py app/*.py
```
(テスト配信は push イベントなので無視されるのが正常)
### 3. 実際に Issue を作成
適当なリポジトリで Issue を作成してみましょう。bridge.py のログに:
### mainへ反映
```bash
git switch main
git pull origin main
git merge --no-ff fix/issue-123-xxx
git push origin main
```
2026-02-25 12:01:00 [INFO] Spawning agent for user/repo#1: テストIssue
2026-02-25 12:01:01 [INFO] Running openclaw agent for label=gitea-review-user-repo-1
```
と出て、しばらくすると Issue にレビュー結果のコメントが付きます 🎉
## まとめ
Gitea + Webhook + Python ブリッジ + OpenClaw という組み合わせで、**Issue を立てるだけで AI が自動レビューしてくれる仕組み**が作れました
Gitea × OpenClaw 連携は、実装より運用設計が重要です。
構成要素はすべてセルフホストで、外部サービスに依存しないのがポイントです。Python の標準ライブラリだけで書いているので、依存管理も不要。
- イベント対応Issue + PR
- 例外時のHTTP応答保証
- ブランチ/コメントテンプレの固定
- 機能分離(リポジトリ・サービス)
### 今後の拡張アイデア
- **ラベルフィルタ**: 特定のラベル(例: `ai-review`)が付いた Issue だけ処理する
- **PR 自動作成**: レビュー結果を Issue コメントだけでなく、修正 PR として作成する
- **Push イベント対応**: push 時にも自動レビューを走らせる
- **Slack / Discord 通知**: レビュー完了を通知する
セルフホスト環境で AI を活用した開発フローを作りたい人の参考になれば嬉しいです!
ここを押さえると、連携はかなり安定します。
---
*この記事は2026年2月時点の情報です。*

View File

@ -25,4 +25,3 @@ slug: /
- [SearXNGでローカル検索APIを構築する](/tech/searxng-local-search/)
- [ローカルGitea × Webhook連携で、AIとの開発をもっと楽にしよう](/tech/gitea-webhook-ai-review/) - Issue作成だけでAIが自動コードレビューしてくれる仕組み
- [Gitea × OpenClaw 連携の開発プロセス実践ガイド](/tech/gitea-openclaw-dev-process/) - Issue/PR駆動で壊れにくく回す実運用ルール

View File

@ -18,12 +18,6 @@ function HomepageHeader() {
function RecentTech() {
const posts = [
{
title: "Gitea × OpenClaw 連携の開発プロセス実践ガイド",
date: "2026-02-27",
tag: "Gitea",
url: "/tech/gitea-openclaw-dev-process",
},
{
title: "ローカルGitea × Webhook連携でAI自動コードレビュー",
date: "2026-02-26",