"搭建代码审查助手"
"本文介绍如何使用 OpenClaw 搭建一个代码审查助手,通过 GitHub webhook 接收 PR 事件并使用子智能体进行代码审查任务委派。"
搭建代码审查助手
本文聚焦:GitHub webhook 配置与接收 + 使用子智能体进行代码审查任务委派
知识点 1:GitHub webhook 配置与接收
是什么
GitHub webhook 是一种实时通知机制,允许 GitHub 在特定事件(如推送、PR 创建、问题评论等)发生时向指定的 URL 发送 HTTP POST 请求。对于代码审查助手来说,webhook 是接收新 PR 事件的关键入口,使得我们可以自动触发代码审查流程。
Webhook 包含了丰富的事件数据,如 PR 的标题、描述、提交的文件列表、差异内容等,为后续的代码分析提供了必要的上下文信息。
为什么需要
传统的代码审查通常依赖于手动触发或定时扫描,这种方式存在明显的延迟和效率问题。通过 webhook,我们可以实现:
- 实时响应:PR 创建或更新时立即收到通知,无需等待定时扫描
- 减少轮询开销:避免频繁查询 GitHub API,降低 API 配额消耗
- 自动化流程:建立从 PR 创建到代码审查的完整自动化流水线
- 精确触发:只对特定事件(如 PR 打开、同步)进行响应,避免不必要的处理
怎么用(代码示例)
首先,我们需要创建一个简单的 webhook 服务器来接收 GitHub 的 POST 请求:
// webhook-server.js - 运行在 Node.js 环境
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.raw({ type: 'application/json' }));
// 验证 GitHub webhook 签名
function verifySignature(payload, signature, secret) {
const expectedSignature = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhook', async (req, res) => {
try {
const signature = req.headers['x-hub-signature-256'];
const event = req.headers['x-github-event'];
const payload = req.body;
// 验证签名(防止恶意请求)
if (!signature || !verifySignature(payload, signature, process.env.GITHUB_WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
// 处理不同的事件类型
if (event === 'pull_request') {
const prData = JSON.parse(payload);
// 只处理打开和同步的 PR 事件
if (prData.action === 'opened' || prData.action === 'synchronize') {
console.log(`Received ${prData.action} event for PR #${prData.number}`);
// 触发代码审查逻辑
await triggerCodeReview(prData);
}
}
res.status(200).send('OK');
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).send('Internal Server Error');
}
});
async function triggerCodeReview(prData) {
// 调用 OpenClaw 的 subagents 工具启动审查子智能体
console.log(`Triggering code review for PR #${prData.number}`);
// 具体实现在知识点 2 中讲解
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
启动 webhook 服务器:
# 设置环境变量
export GITHUB_WEBHOOK_SECRET="your-webhook-secret"
# 安装依赖并启动
npm install express
node webhook-server.js
接下来,在 GitHub 仓库中配置 webhook:
# 使用 GitHub API 创建 webhook(需要设置 GITHUB_TOKEN 环境变量)
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$OWNER/$REPO/hooks \
-d '{
"name": "web",
"active": true,
"events": ["pull_request"],
"config": {
"url": "https://your-domain.com/webhook",
"content_type": "json",
"secret": "your-webhook-secret",
"insecure_ssl": "0"
}
}'
安全提示:GITHUB_TOKEN 应该存储在环境变量或密钥管理系统中,不要硬编码在代码里。
常见陷阱
- 安全性问题:未验证 webhook 签名可能导致恶意请求伪造,务必启用签名验证
- 重试机制:GitHub 会在请求失败时重试,需要确保处理逻辑幂等(同一事件多次处理结果一致)
- API 速率限制:处理 webhook 时调用 GitHub API 需要考虑速率限制(每小时 5000 次请求)
- 网络可达性:webhook 地址必须对外网可访问,内网环境需配置反向代理(如 ngrok)
- 错误处理:异常情况下需要适当的错误记录和告警机制,避免静默失败
知识点 2:使用子智能体进行代码审查任务委派
是什么
子智能体(Subagent)是 OpenClaw 中用于处理特定任务的轻量级智能体实例。在代码审查场景中,每当有新的 PR 事件到达时,主智能体会创建一个专门的子智能体来负责该 PR 的代码审查工作。这种模式实现了任务隔离、并发处理和资源优化。
子智能体继承了主智能体的能力,但专注于特定的代码审查任务,可以在独立的环境中分析代码差异、生成审查意见、并与 GitHub 进行交互。
为什么需要
使用子智能体进行代码审查任务委派有以下优势:
- 任务隔离:每个 PR 的审查在独立的子智能体中进行,避免相互干扰
- 并发处理:多个 PR 可以同时进行审查,提高整体效率
- 资源管理:可以根据负载动态调整子智能体数量,优化资源利用
- 状态管理:子智能体可以维护单个 PR 的审查状态,便于跟踪进度
- 错误隔离:某个子智能体失败不会影响其他审查任务
怎么用(代码示例)
在 OpenClaw 中,我们可以使用 sessions_spawn 工具来创建子智能体进行代码审查:
// code-review-handler.js - 在 OpenClaw Agent 中运行
async function handlePullRequest(prData) {
// 创建代码审查子智能体
const subagent = await sessions_spawn({
task: `审查 PR #${prData.number} 的代码变更,仓库:${prData.repository.full_name}`,
runtime: 'subagent',
mode: 'run',
agentId: 'code-reviewer',
attachments: [
{
name: 'pr-info.json',
content: JSON.stringify(prData, null, 2),
mimeType: 'application/json'
}
]
});
console.log(`Created subagent for PR #${prData.number}: ${subagent.runId}`);
// 使用 subagents 工具查询子智能体状态
const result = await pollSubagentResult(subagent.runId);
// 将审查结果提交到 GitHub
await postReviewComment(prData, result);
}
// 轮询子智能体结果(简单实现)
async function pollSubagentResult(runId, maxAttempts = 30) {
for (let i = 0; i < maxAttempts; i++) {
const status = await subagents({
action: 'list',
recentMinutes: 5
});
// 查找对应的子智能体
const subagent = status.recent.find(s => s.runId === runId);
if (subagent && subagent.status === 'done') {
return subagent.result;
}
// 等待 10 秒后重试
await new Promise(resolve => setTimeout(resolve, 10000));
}
throw new Error('Subagent timeout');
}
module.exports = { handlePullRequest };
子智能体内部的审查逻辑:
// code-reviewer agent - 子智能体中执行的任务
async function reviewCode() {
// 1. 从附件中读取 PR 信息
const prInfo = JSON.parse(attachments['pr-info.json'].content);
// 2. 获取代码差异(使用 web_fetch 工具)
const diffContent = await web_fetch({
url: prInfo.pull_request.diff_url,
extractMode: 'text'
});
// 3. 分析代码变更
const reviewResult = await analyzeCodeChanges(diffContent);
// 4. 返回审查报告
return {
prNumber: prInfo.number,
issues: reviewResult.issues,
suggestions: reviewResult.suggestions,
summary: reviewResult.summary
};
}
async function analyzeCodeChanges(diffContent) {
const issues = [];
const suggestions = [];
// 示例:检查常见的代码问题
if (diffContent.includes('console.log')) {
issues.push({
type: 'warning',
message: '发现 console.log,建议在生产代码中移除'
});
}
if (diffContent.includes('TODO') || diffContent.includes('FIXME')) {
suggestions.push({
type: 'suggestion',
message: '发现 TODO/FIXME 注释,建议创建 issue 跟踪'
});
}
// 可以调用 LLM 进行更深入的代码分析
// const llmResult = await sessions_send({ ... });
return {
issues,
suggestions,
summary: `发现 ${issues.length} 个问题,${suggestions.length} 条建议`
};
}
async function postReviewComment(prData, reviewResult) {
// 格式化审查评论
const commentBody = formatReviewComment(reviewResult);
// 使用 exec 工具调用 GitHub API
await exec({
command: `curl -X POST \\
-H "Authorization: token ${process.env.GITHUB_TOKEN}" \\
-H "Accept: application/vnd.github.v3+json" \\
https://api.github.com/repos/${prData.repository.full_name}/issues/${prData.number}/comments \\
-d '${JSON.stringify({ body: commentBody })}'`,
timeout: 30
});
}
function formatReviewComment(result) {
let comment = '## 🤖 代码审查报告\\n\\n';
comment += `**PR #${result.prNumber}**\\n\\n`;
if (result.issues.length > 0) {
comment += '### ⚠️ 发现的问题\\n';
result.issues.forEach(issue => {
comment += `- **${issue.type}**: ${issue.message}\\n`;
});
comment += '\\n';
}
if (result.suggestions.length > 0) {
comment += '### 💡 建议\\n';
result.suggestions.forEach(suggestion => {
comment += `- ${suggestion.message}\\n`;
});
comment += '\\n';
}
comment += `**总结**: ${result.summary}`;
return comment;
}
常见陷阱
- 上下文传递:PR 数据传递给子智能体时需要确保格式正确,建议使用 JSON 附件传递结构化数据
- 超时控制:代码审查可能耗时较长,需要设置合理的超时时间(建议 5-10 分钟)
- 结果获取:子智能体完成后需要通过
subagents工具查询结果,注意轮询间隔避免频繁调用 - 权限管理:子智能体访问 GitHub API 需要正确的 token 权限(
repo或public_repo) - 成本控制:频繁创建子智能体可能增加 API 调用成本,建议对同一 PR 的多次更新进行去重处理
实践建议
-
分层审查策略:将代码审查分为快速检查(语法、格式)和深度检查(逻辑、架构)两层,分别由不同的子智能体处理
-
增量审查:只审查变更的文件,避免全量代码分析,提高效率。可以通过
prInfo.pull_request.changed_files获取变更文件列表 -
配置化规则:将审查规则提取到配置文件,便于团队统一维护和调整
// review-rules.json
{
"rules": [
{ "pattern": "console.log", "level": "warning", "message": "移除调试代码" },
{ "pattern": "TODO|FIXME", "level": "suggestion", "message": "创建 issue 跟踪" }
]
}
-
人机协作:将 AI 审查作为人工审查的辅助,而非替代,对于关键代码仍需人工确认。可以在审查报告中添加 "需要人工复核" 的标记
-
反馈闭环:收集开发人员对审查意见的反馈,持续优化审查规则和提示词。建议定期统计误报率和漏报率