首页 › tutorials › Claude Code Agent SDK与子进程编排全实战

概述

本教程面向希望将 Claude Code 从「交互式终端工具」升级为「可编程自动化引擎」的开发者。你将掌握三大核心能力:Headless 模式的完整参数体系、Agent SDK 的 TypeScript 子进程集成、以及 Hooks 生命周期事件的实战配置。适合有一定 Claude Code 使用基础、需要在 CI/CD 管道、批量脚本或多 Agent 编排场景中自动化运行的中高级开发者。

学习目标: - 理解 Claude Code 的三种非交互执行模式及其适用场景 - 使用 Agent SDK 构建可编程的 Claude Code 子进程调用 - 配置 Hooks 实现工具调用的前后拦截与自动化 - 完成一个多仓库批量代码审查的端到端自动化项目

前置条件

条件 说明
Claude Code CLI 已安装并完成 OAuth 认证(claude 命令可用)
Node.js 18+ Agent SDK 运行环境
TypeScript 示例代码使用 TS,可按需降级为 JS
基础 Shell 了解管道、重定向、退出码等概念
Claude Code 使用经验 至少在交互模式下完成过一个项目

第一部分:Headless 模式深度解析

1.1 三种非交互执行模式

Claude Code 提供三种脱离 TUI(终端用户界面)的执行方式,对应不同自动化需求:

┌─────────────────────────────────────────────────────────┐
│              Claude Code 非交互执行模式                    │
├──────────────┬──────────────┬───────────────────────────┤
│  Print 模式   │  Stream 模式  │     Agent SDK 模式        │
│  claude -p    │  --output-   │  @anthropic-ai/          │
│              │  format json  │  claude-agent-sdk         │
├──────────────┼──────────────┼───────────────────────────┤
│ 单次执行      │ 流式输出      │ 可编程子进程               │
│ 文本/JSON输出 │ 事件驱动      │ 完整事件监听               │
│ 适合脚本      │ 适合实时监控  │ 适合应用集成               │
│ 最简集成      │ 中等复杂度    │ 最高灵活度                 │
└──────────────┴──────────────┴───────────────────────────┘

1.2 Print 模式(-p)完整参数

Print 模式是最轻量的非交互方式,一行命令即可获取 Claude 的输出:

# 基础用法:直接输出文本
claude -p "解释这个函数的作用"

# 从 stdin 读取输入(管道)
cat src/auth.ts | claude -p "审查这段代码的安全性问题"

# 输出为 JSON 格式
claude -p "列出所有 TODO 注释" --output-format json

# 流式 JSON(实时获取事件)
claude -p "重构这个模块" --output-format stream-json

# 限制执行轮次(防止失控)
claude -p "修复所有 lint 错误" --max-turns 15

# 指定允许的工具
claude -p "检查测试覆盖率" --allowedTools "Bash,Read,Glob,Grep"

# 跳过权限确认(仅限 CI/容器环境)
claude -p "运行全部测试" --dangerously-skip-permissions

# 组合使用:CI 场景的典型模式
claude -p "审查 PR 的变更内容并给出改进建议" \
  --output-format json \
  --max-turns 10 \
  --allowedTools "Read,Grep,Glob" \
  --dangerously-skip-permissions

关键参数详解:

参数 作用 推荐值
-p / --print 非交互模式,处理完自动退出 必需
--output-format text(默认)、json、stream-json 脚本用 json,监控用 stream-json
--max-turns 限制 agent 循环的最大轮次 CI 场景建议 5-20
--allowedTools 白名单允许的工具列表 按需最小化授权
--dangerously-skip-permissions 跳过所有权限确认 仅限受控环境
--model 指定使用的模型 按成本/质量权衡
--resume 恢复上一次对话 批量任务续跑

1.3 输出格式详解

JSON 输出(--output-format json):

claude -p "列出项目中的 TypeScript 文件" --output-format json

返回结构:

{
  "result": "项目中共有 47 个 TypeScript 文件...",
  "cost_usd": 0.0034,
  "duration_ms": 5230,
  "num_turns": 2,
  "session_id": "abc123-def456"
}

流式 JSON(--output-format stream-json):

claude -p "重构 auth 模块" --output-format stream-json

实时输出事件流:

{"type":"assistant","subtype":"text","text":"我来分析 auth 模块的结构..."}
{"type":"tool_use","tool":"Read","input":{"file_path":"/src/auth/login.ts"}}
{"type":"tool_result","tool":"Read","output":"..."}
{"type":"assistant","subtype":"text","text":"发现以下可改进的点..."}

流式模式允许你在脚本中实时捕获每个工具调用和文本输出,适用于: - 实时监控长时间运行的任务 - 构建自定义进度仪表盘 - 在工具调用间注入逻辑

1.4 Shell 脚本集成模式

#!/bin/bash
# batch-review.sh - 批量审查多个文件

set -euo pipefail

FILES=("src/auth.ts" "src/api.ts" "src/db.ts")
RESULTS_DIR="./review-results"
mkdir -p "$RESULTS_DIR"

for file in "${FILES[@]}"; do
  echo "🔍 审查: $file"

  result=$(claude -p "审查这个文件的安全性和代码质量,输出改进建议:$(cat "$file")" \
    --output-format json \
    --max-turns 5)

  # 提取结果文本
  summary=$(echo "$result" | jq -r '.result')
  cost=$(echo "$result" | jq -r '.cost_usd')

  echo "$summary" > "$RESULTS_DIR/$(basename "$file" .ts).md"
  echo "  ✅ 完成 (成本: \$$cost)"
done

echo "📊 所有审查结果已保存到 $RESULTS_DIR/"

1.5 常见问题与解决方案

问题 原因 解决方案
输出被截断 默认 --max-turns 太低 增大到 10-20,或设为 0(无限制)
CI 中卡住等待权限 未跳过权限确认 使用 --dangerously-skip-permissions + Docker 沙箱
API 费用超预期 Agent 循环次数失控 始终设置 --max-turns 上限
JSON 解析失败 输出包含非 JSON 内容 确认使用 --output-format json
stdin 内容丢失 管道与 -p 文本冲突 使用 cat file \| claude -p "指令" 格式

第二部分:Agent SDK 子进程编排

2.1 SDK 架构概览

Agent SDK(@anthropic-ai/claude-agent-sdk)是官方提供的 TypeScript 库,它以子进程方式调用 Claude Code CLI,提供类型安全的事件流接口:

graph TD
    A[你的 Node.js 应用] -->|import SDK| B[ClaudeAgent SDK]
    B -->|spawn 子进程| C[Claude Code CLI]
    C -->|执行工具调用| D[文件系统 / Shell / MCP]
    D -->|返回结果| C
    C -->|事件流 JSONL| B
    B -->|类型化事件| A

    style A fill:#4A90D9,color:#fff
    style B fill:#7B68EE,color:#fff
    style C fill:#FF6B6B,color:#fff
    style D fill:#51CF66,color:#fff

核心概念: - Host-Guest 模型:你的应用是 Host,Claude Code 是被管理的 Guest 子进程 - 事件驱动:所有交互通过事件流完成,不依赖标准输入输出 - 工具隔离:子进程继承 CLI 的所有工具能力,但权限可独立控制

2.2 安装与初始化

# 安装 SDK
npm install @anthropic-ai/claude-agent-sdk

# 确保 Claude Code CLI 已安装并认证
claude --version

最小可用示例:

// minimal-agent.ts
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';

async function main() {
  const agent = new ClaudeCode({
    prompt: '列出当前目录下的所有 TypeScript 文件,统计总行数',
    options: {
      outputFormat: 'json',
      maxTurns: 5,
    },
  });

  const result = await agent.run();
  console.log('结果:', result.result);
  console.log('成本:', result.cost_usd);
  console.log('轮次:', result.num_turns);
}

main().catch(console.error);

运行:

npx tsx minimal-agent.ts

2.3 流式事件监听

对于长时间运行的任务,流式监听是关键能力:

// stream-agent.ts
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';

async function streamReview() {
  const agent = new ClaudeCode({
    prompt: '审查 src/ 目录下所有文件的代码质量',
    options: {
      outputFormat: 'stream-json',
      maxTurns: 20,
      allowedTools: ['Read', 'Grep', 'Glob'],
    },
  });

  // 监听各类事件
  agent.on('assistant', (event) => {
    // Claude 输出的文本
    process.stdout.write(event.text);
  });

  agent.on('tool_use', (event) => {
    // 工具被调用
    console.log(`\n🔧 使用工具: ${event.tool}`);
    if (event.tool === 'Read') {
      console.log(`   📄 读取: ${event.input.file_path}`);
    }
  });

  agent.on('tool_result', (event) => {
    // 工具返回结果
    const preview = typeof event.output === 'string'
      ? event.output.slice(0, 100) + '...'
      : JSON.stringify(event.output).slice(0, 100);
    console.log(`   📋 结果: ${preview}`);
  });

  agent.on('error', (event) => {
    console.error('❌ 错误:', event.message);
  });

  const result = await agent.run();
  console.log('\n✅ 完成');
  console.log(`总成本: $${result.cost_usd}`);
  console.log(`总轮次: ${result.num_turns}`);
}

streamReview().catch(console.error);

2.4 多 Agent 并行编排

这是 Agent SDK 的杀手级应用——同时运行多个 Claude Code 实例并行处理任务:

// parallel-agents.ts
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';

interface ReviewResult {
  file: string;
  summary: string;
  issues: string[];
  cost: number;
}

async function reviewFile(filePath: string): Promise<ReviewResult> {
  const agent = new ClaudeCode({
    prompt: `审查文件 ${filePath} 的代码质量。关注:
1. 安全漏洞
2. 性能问题
3. 代码可维护性
以 JSON 格式输出: {"summary": "...", "issues": ["..."]}`,
    options: {
      outputFormat: 'json',
      maxTurns: 8,
      allowedTools: ['Read', 'Grep', 'Glob'],
    },
  });

  const result = await agent.run();

  try {
    const parsed = JSON.parse(result.result);
    return {
      file: filePath,
      summary: parsed.summary || result.result,
      issues: parsed.issues || [],
      cost: result.cost_usd,
    };
  } catch {
    return {
      file: filePath,
      summary: result.result,
      issues: [],
      cost: result.cost_usd,
    };
  }
}

async function parallelReview() {
  const files = [
    'src/auth/login.ts',
    'src/auth/register.ts',
    'src/api/users.ts',
    'src/api/products.ts',
    'src/db/connection.ts',
  ];

  console.log(`🚀 启动 ${files.length} 个并行审查 Agent...\n`);

  // Promise.allSettled 确保单个失败不影响整体
  const results = await Promise.allSettled(
    files.map(file => reviewFile(file))
  );

  // 汇总报告
  let totalCost = 0;
  let totalIssues = 0;

  results.forEach((r, i) => {
    if (r.status === 'fulfilled') {
      const { file, summary, issues, cost } = r.value;
      totalCost += cost;
      totalIssues += issues.length;

      console.log(`\n📄 ${file}`);
      console.log(`   摘要: ${summary.slice(0, 100)}`);
      console.log(`   问题数: ${issues.length}`);
      issues.forEach(issue => console.log(`   ⚠️  ${issue}`));
    } else {
      console.log(`\n❌ ${files[i]} 审查失败: ${r.reason}`);
    }
  });

  console.log(`\n${'='.repeat(50)}`);
  console.log(`📊 汇总: ${files.length} 文件 | ${totalIssues} 问题 | $${totalCost.toFixed(4)}`);
}

parallelReview().catch(console.error);
graph LR
    A[主进程] -->|spawn| B1[Agent 1: auth/login.ts]
    A -->|spawn| B2[Agent 2: auth/register.ts]
    A -->|spawn| B3[Agent 3: api/users.ts]
    A -->|spawn| B4[Agent 4: api/products.ts]
    A -->|spawn| B5[Agent 5: db/connection.ts]

    B1 -->|结果| C[汇总报告]
    B2 -->|结果| C
    B3 -->|结果| C
    B4 -->|结果| C
    B5 -->|结果| C

    style A fill:#4A90D9,color:#fff
    style C fill:#51CF66,color:#fff

2.5 SDK 高级配置

// advanced-config.ts
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';

const agent = new ClaudeCode({
  prompt: '执行全面的安全审计',
  options: {
    // 输出格式
    outputFormat: 'stream-json',

    // 执行控制
    maxTurns: 30,
    model: 'claude-sonnet-4-6',  // 选择模型(成本优化)

    // 工具控制
    allowedTools: ['Read', 'Grep', 'Glob', 'Bash'],

    // 权限控制
    dangerouslySkipPermissions: false,

    // 恢复会话
    resume: 'previous-session-id',

    // 系统提示(项目级)
    systemPrompt: '你是一个安全审计专家,专注于 OWASP Top 10',
  },
});

// 超时控制
const TIMEOUT_MS = 120_000; // 2 分钟
const timeout = setTimeout(() => {
  console.error('⏰ 超时,终止 Agent');
  agent.abort();
}, TIMEOUT_MS);

try {
  const result = await agent.run();
  console.log('完成:', result.result);
} finally {
  clearTimeout(timeout);
}

第三部分:Hooks 生命周期自动化

3.1 Hooks 事件体系

Hooks 是嵌入 Claude Code 执行生命周期的用户自定义 Shell 命令。它们在特定事件触发时自动执行,无需修改 Claude Code 本身:

sequenceDiagram
    participant U as 用户
    participant CC as Claude Code
    participant H as Hooks 层
    participant FS as 文件系统

    U->>CC: 发送请求
    CC->>H: PreToolUse 事件
    H-->>CC: 允许 / 阻止 / 修改

    alt Hook 允许
        CC->>FS: 执行工具调用
        FS-->>CC: 返回结果
        CC->>H: PostToolUse 事件
        H-->>CC: 处理完毕
    else Hook 阻止
        CC-->>U: 工具被拦截
    end

    CC->>H: Stop 事件
    H-->>CC: 通知处理
    CC-->>U: 返回最终结果

核心事件类型:

事件 触发时机 典型用途
PreToolUse 工具执行前 拦截危险操作、注入上下文
PostToolUse 工具执行后 自动格式化、日志记录
Notification 通知触发时 Slack/邮件通知、CI 状态更新
Stop Agent 停止时 最终汇总、报告生成
SessionStart 会话开始时 加载配置、环境检查

3.2 Hooks 配置文件

Hooks 在 settings.json 中配置(项目级 .claude/settings.json 或全局 ~/.claude/settings.json):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "cat"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null || true"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "curl -s -X POST \"$SLACK_WEBHOOK\" -H 'Content-type: application/json' --data '{\"text\":\"🔔 Claude Code 任务完成\"}'"
          }
        ]
      }
    ]
  }
}

3.3 Hook 的 JSON 输入输出

每个 Hook 通过 stdin 接收 JSON 输入,通过 stdout 返回 JSON 输出:

PreToolUse 输入(stdin):

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules"
  },
  "session_id": "abc-123"
}

PreToolUse 输出(stdout):

{
  "decision": "block",
  "reason": "禁止执行 rm -rf 命令"
}

决策值: - approve — 允许执行(默认行为) - block — 阻止执行并返回原因 - 不输出 JSON — 默认允许

PostToolUse 输入(stdin):

{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/src/auth.ts",
    "old_string": "...",
    "new_string": "..."
  },
  "tool_output": "File edited successfully",
  "session_id": "abc-123"
}

3.4 实战 Hook 脚本

Hook 1:阻止危险 Shell 命令

#!/bin/bash
# hooks/block-dangerous.sh
# 读取 stdin 的 JSON 输入
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# 危险命令黑名单
DANGEROUS_PATTERNS=(
  "rm -rf /"
  "rm -rf ~"
  "DROP TABLE"
  ":(){ :|:& };:"
  "dd if=/dev/zero"
  "mkfs"
  "> /dev/sda"
)

for pattern in "${DANGEROUS_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qi "$pattern"; then
    # 输出 block 决策
    jq -n --arg reason "🚫 检测到危险命令模式: $pattern" \
      '{decision: "block", reason: $reason}'
    exit 0
  fi
done

# 默认允许
echo '{"decision": "approve"}'

Hook 2:自动格式化编辑过的文件

#!/bin/bash
# hooks/auto-format.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [ -z "$FILE_PATH" ]; then
  exit 0
fi

# 根据文件扩展名选择格式化工具
case "$FILE_PATH" in
  *.ts|*.tsx|*.js|*.jsx)
    npx prettier --write "$FILE_PATH" 2>/dev/null
    npx eslint --fix "$FILE_PATH" 2>/dev/null
    ;;
  *.py)
    black "$FILE_PATH" 2>/dev/null
    isort "$FILE_PATH" 2>/dev/null
    ;;
  *.go)
    gofmt -w "$FILE_PATH" 2>/dev/null
    ;;
esac

Hook 3:保护敏感文件

#!/bin/bash
# hooks/protect-secrets.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# 敏感文件保护列表
PROTECTED_FILES=(
  ".env"
  ".env.production"
  "credentials.json"
  "service-account-key.json"
  "id_rsa"
)

BASENAME=$(basename "$FILE_PATH")
for protected in "${PROTECTED_FILES[@]}"; do
  if [ "$BASENAME" = "$protected" ] || echo "$FILE_PATH" | grep -q "$protected"; then
    jq -n --arg file "$FILE_PATH" \
      '{decision: "block", reason: "🔒 禁止修改敏感文件: \($file)"}'
    exit 0
  fi
done

echo '{"decision": "approve"}'

完整 settings.json 配置:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/block-dangerous.sh"
          }
        ]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/protect-secrets.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/auto-format.sh"
          }
        ]
      }
    ]
  }
}

第四部分:实战项目——多仓库批量代码审查系统

4.1 项目架构

我们将构建一个完整的自动化系统,它能: 1. 扫描多个 Git 仓库 2. 检测最近的变更文件 3. 为每个文件启动独立的审查 Agent 4. 汇总生成 Markdown 报告 5. 通过 Hook 自动发送通知

graph TB
    subgraph "批量审查系统"
        A[主控脚本<br/>batch-review.ts] --> B[扫描仓库列表]
        B --> C{遍历仓库}
        C --> D[git pull & git diff]
        D --> E[提取变更文件]
        E --> F[启动并行 Agent]

        F --> G1[Agent 1<br/>审查文件A]
        F --> G2[Agent 2<br/>审查文件B]
        F --> G3[Agent 3<br/>审查文件C]

        G1 --> H[汇总结果]
        G2 --> H
        G3 --> H

        H --> I[生成 Markdown 报告]
        I --> J[Hook: 发送通知]
    end

    style A fill:#4A90D9,color:#fff
    style H fill:#51CF66,color:#fff
    style J fill:#FFD93D,color:#333

4.2 完整实现

项目结构:

batch-review/
├── src/
│   ├── main.ts              # 主入口
│   ├── scanner.ts           # 仓库扫描器
│   ├── reviewer.ts          # Agent 审查器
│   └── reporter.ts          # 报告生成器
├── .claude/
│   ├── hooks/
│   │   └── notify.sh        # 通知 Hook
│   └── settings.json        # Hooks 配置
├── package.json
└── tsconfig.json

package.json:

{
  "name": "batch-review",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "review": "tsx src/main.ts",
    "review:dry": "tsx src/main.ts --dry-run"
  },
  "dependencies": {
    "@anthropic-ai/claude-agent-sdk": "latest"
  },
  "devDependencies": {
    "tsx": "^4.7.0",
    "typescript": "^5.4.0"
  }
}

src/scanner.ts — 仓库扫描器:

import { execSync } from 'child_process';
import path from 'path';

export interface RepoChange {
  repo: string;
  file: string;
  status: 'added' | 'modified' | 'deleted';
  diff: string;
}

export interface RepoConfig {
  path: string;
  branch?: string;
  since?: string; // git log --since 格式
}

export function scanRepo(config: RepoConfig): RepoChange[] {
  const { path: repoPath, branch = 'main', since = '1 day ago' } = config;

  try {
    // 拉取最新代码
    execSync(`git -C "${repoPath}" pull origin ${branch}`, { encoding: 'utf-8' });

    // 获取变更文件列表
    const changedFiles = execSync(
      `git -C "${repoPath}" diff --name-status HEAD~5..HEAD`,
      { encoding: 'utf-8' }
    ).trim().split('\n').filter(Boolean);

    return changedFiles.map(line => {
      const [status, ...fileParts] = line.split('\t');
      const file = fileParts.join('\t');

      // 只审查代码文件
      if (!file.match(/\.(ts|tsx|js|jsx|py|go|rs|java)$/)) {
        return null;
      }

      // 获取 diff 内容
      let diff = '';
      try {
        diff = execSync(
          `git -C "${repoPath}" diff HEAD~5..HEAD -- "${file}"`,
          { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }
        );
      } catch { /* diff 太大或文件已删除 */ }

      return {
        repo: path.basename(repoPath),
        file,
        status: status as RepoChange['status'],
        diff: diff.slice(0, 5000), // 限制 diff 大小
      };
    }).filter(Boolean) as RepoChange[];
  } catch (error) {
    console.error(`扫描 ${repoPath} 失败:`, error);
    return [];
  }
}

src/reviewer.ts — Agent 审查器:

import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';
import { RepoChange } from './scanner.js';

export interface ReviewResult {
  repo: string;
  file: string;
  approved: boolean;
  summary: string;
  issues: Array<{
    severity: 'critical' | 'warning' | 'info';
    line?: number;
    message: string;
  }>;
  cost: number;
}

const REVIEW_PROMPT = `你是一个严格的代码审查专家。审查以下代码变更,关注:

1. **安全漏洞** — SQL 注入、XSS、认证绕过、敏感信息泄露
2. **逻辑错误** — 边界条件、空指针、竞态条件
3. **性能问题** — N+1 查询、内存泄漏、不必要的循环
4. **可维护性** — 命名规范、代码重复、过度复杂

以以下 JSON 格式输出(不要输出其他内容):
{
  "approved": true/false,
  "summary": "一句话总结",
  "issues": [
    {"severity": "critical|warning|info", "line": 42, "message": "描述"}
  ]
}`;

export async function reviewChange(change: RepoChange): Promise<ReviewResult> {
  const prompt = `${REVIEW_PROMPT}\n\n仓库: ${change.repo}\n文件: ${change.file}\n变更状态: ${change.status}\n\nDiff:\n\`\`\`diff\n${change.diff}\n\`\`\``;

  const agent = new ClaudeCode({
    prompt,
    options: {
      outputFormat: 'json',
      maxTurns: 5,
      model: 'claude-sonnet-4-6', // 成本优化
      allowedTools: [], // 审查不需要工具
    },
  });

  const result = await agent.run();

  try {
    // 尝试从结果中提取 JSON
    const text = result.result;
    const jsonMatch = text.match(/\{[\s\S]*\}/);
    if (jsonMatch) {
      const parsed = JSON.parse(jsonMatch[0]);
      return {
        repo: change.repo,
        file: change.file,
        approved: parsed.approved ?? true,
        summary: parsed.summary ?? '',
        issues: parsed.issues ?? [],
        cost: result.cost_usd,
      };
    }
  } catch { /* JSON 解析失败 */ }

  return {
    repo: change.repo,
    file: change.file,
    approved: true,
    summary: result.result,
    issues: [],
    cost: result.cost_usd,
  };
}

export async function reviewChangesParallel(
  changes: RepoChange[],
  concurrency: number = 3
): Promise<ReviewResult[]> {
  const results: ReviewResult[] = [];

  // 分批并行执行
  for (let i = 0; i < changes.length; i += concurrency) {
    const batch = changes.slice(i, i + concurrency);
    const batchResults = await Promise.allSettled(
      batch.map(change => reviewChange(change))
    );

    batchResults.forEach((r, j) => {
      if (r.status === 'fulfilled') {
        results.push(r.value);
      } else {
        console.error(`审查失败 ${batch[j].repo}/${batch[j].file}:`, r.reason);
      }
    });

    console.log(`📊 进度: ${Math.min(i + concurrency, changes.length)}/${changes.length}`);
  }

  return results;
}

src/reporter.ts — 报告生成器:

import fs from 'fs';
import { ReviewResult } from './reviewer.js';

export function generateReport(results: ReviewResult[]): string {
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
  const totalCost = results.reduce((sum, r) => sum + r.cost, 0);
  const approved = results.filter(r => r.approved);
  const rejected = results.filter(r => !r.approved);
  const criticalIssues = results.flatMap(r => 
    r.issues.filter(i => i.severity === 'critical')
  );

  let report = `# 🤖 批量代码审查报告\n\n`;
  report += `**生成时间**: ${new Date().toLocaleString('zh-CN')}\n`;
  report += `**审查文件**: ${results.length} 个\n`;
  report += `**通过**: ${approved.length} | **需修改**: ${rejected.length}\n`;
  report += `**严重问题**: ${criticalIssues.length} 个\n`;
  report += `**总成本**: $${totalCost.toFixed(4)}\n\n`;

  // 严重问题优先
  if (criticalIssues.length > 0) {
    report += `## 🚨 严重问题\n\n`;
    criticalIssues.forEach(issue => {
      const file = results.find(r => 
        r.issues.some(i => i.message === issue.message)
      );
      report += `- **${file?.repo}/${file?.file}**`;
      if (issue.line) report += `:${issue.line}`;
      report += ` — ${issue.message}\n`;
    });
    report += '\n';
  }

  // 按仓库分组
  const byRepo = groupBy(results, r => r.repo);
  for (const [repo, repoResults] of byRepo) {
    report += `## 📦 ${repo}\n\n`;
    repoResults.forEach(r => {
      const icon = r.approved ? '✅' : '❌';
      report += `### ${icon} ${r.file}\n\n`;
      report += `${r.summary}\n\n`;
      if (r.issues.length > 0) {
        report += `| 严重度 | 行号 | 描述 |\n`;
        report += `|--------|------|------|\n`;
        r.issues.forEach(i => {
          const sevIcon = i.severity === 'critical' ? '🔴' : 
                          i.severity === 'warning' ? '🟡' : '🔵';
          report += `| ${sevIcon} ${i.severity} | ${i.line ?? '-'} | ${i.message} |\n`;
        });
        report += '\n';
      }
    });
  }

  // 保存报告
  const reportPath = `./reports/review-${timestamp}.md`;
  fs.mkdirSync('./reports', { recursive: true });
  fs.writeFileSync(reportPath, report);
  console.log(`\n📄 报告已保存: ${reportPath}`);

  return report;
}

function groupBy<T>(arr: T[], key: (item: T) => string): Map<string, T[]> {
  const map = new Map<string, T[]>();
  arr.forEach(item => {
    const k = key(item);
    if (!map.has(k)) map.set(k, []);
    map.get(k)!.push(item);
  });
  return map;
}

src/main.ts — 主入口:

import { scanRepo, RepoConfig } from './scanner.js';
import { reviewChangesParallel } from './reviewer.js';
import { generateReport } from './reporter.js';

// 仓库配置列表
const REPOS: RepoConfig[] = [
  { path: '../backend-api', branch: 'main' },
  { path: '../frontend-app', branch: 'develop' },
  { path: '../shared-libs', branch: 'main' },
];

async function main() {
  const dryRun = process.argv.includes('--dry-run');

  console.log('🔍 开始扫描仓库变更...\n');

  // 第一步:扫描所有仓库
  const allChanges = REPOS.flatMap(config => {
    const changes = scanRepo(config);
    console.log(`  ${config.path}: ${changes.length} 个变更文件`);
    return changes;
  });

  if (allChanges.length === 0) {
    console.log('✅ 没有检测到代码变更');
    return;
  }

  console.log(`\n📊 共 ${allChanges.length} 个文件需要审查\n`);

  if (dryRun) {
    allChanges.forEach(c => {
      console.log(`  [${c.status}] ${c.repo}/${c.file}`);
    });
    return;
  }

  // 第二步:并行审查
  console.log('🚀 启动并行审查...\n');
  const results = await reviewChangesParallel(allChanges, 3);

  // 第三步:生成报告
  console.log('\n📝 生成审查报告...');
  generateReport(results);

  // 第四步:输出统计
  const rejected = results.filter(r => !r.approved);
  if (rejected.length > 0) {
    console.log(`\n⚠️  ${rejected.length} 个文件需要修改:`);
    rejected.forEach(r => console.log(`  ❌ ${r.repo}/${r.file}`));
    process.exit(1); // CI 中触发失败
  }

  console.log('\n✅ 所有文件审查通过!');
}

main().catch(error => {
  console.error('💥 批量审查失败:', error);
  process.exit(1);
});

4.3 配套 Hooks

通知 Hook(.claude/hooks/notify.sh):

#!/bin/bash
# 当 Stop 事件触发时发送通知
INPUT=$(cat)
EVENT_TYPE=$(echo "$INPUT" | jq -r '.stop_hook_message // empty')

if [ -n "$SLACK_WEBHOOK" ]; then
  curl -s -X POST "$SLACK_WEBHOOK" \
    -H 'Content-type: application/json' \
    --data "{\"text\":\"🤖 批量审查完成: $EVENT_TYPE\"}" \
    > /dev/null 2>&1
fi

# 可选:发送邮件
if [ -n "$MAIL_TO" ]; then
  echo "批量代码审查已完成。请查看报告。" | \
    mail -s "代码审查报告 $(date +%Y-%m-%d)" "$MAIL_TO"
fi

4.4 运行与验证

# 安装依赖
npm install

# 试运行(只列出变更文件,不启动 Agent)
npm run review:dry

# 正式运行
npm run review

# 作为定时任务(crontab)
# 每天早上 9 点自动审查
0 9 * * * cd /path/to/batch-review && npm run review >> logs/review.log 2>&1

预期输出:

🔍 开始扫描仓库变更...

  ../backend-api: 5 个变更文件
  ../frontend-app: 3 个变更文件
  ../shared-libs: 2 个变更文件

📊 共 10 个文件需要审查

🚀 启动并行审查...

📊 进度: 3/10
📊 进度: 6/10
📊 进度: 9/10
📊 进度: 10/10

📝 生成审查报告...

📄 报告已保存: ./reports/review-2026-06-05T09-00-00Z.md

⚠️  2 个文件需要修改:
  ❌ backend-api/src/auth/login.ts
  ❌ backend-api/src/api/admin.ts

第五部分:CI/CD 集成模式

5.1 GitHub Actions 集成

# .github/workflows/claude-review.yml
name: Claude Code Review

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Claude Code
        run: npm install -g @anthropic-ai/claude-code

      - name: Run Claude Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          # 获取 PR 变更文件
          CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E '\.(ts|tsx|js|jsx)$' | head -20)

          if [ -z "$CHANGED_FILES" ]; then
            echo "没有代码文件变更"
            exit 0
          fi

          # 对每个文件运行审查
          for file in $CHANGED_FILES; do
            echo "审查: $file"
            claude -p "审查这个文件的变更,关注安全性和代码质量。只报告问题,不要夸赞。

          文件: $file

          $(cat "$file")" \
              --output-format json \
              --max-turns 5 \
              --allowedTools "Read" \
              --dangerously-skip-permissions > "review-$(basename "$file").json"
          done

          # 合并结果为 PR 评论
          node scripts/post-review-comment.js

5.2 成本控制策略

// cost-control.ts — 三级成本控制

// 级别 1:限制执行轮次
const LEVEL_1 = {
  maxTurns: 5,         // 最少轮次
  model: 'claude-haiku-4-5',  // 最便宜的模型
};

// 级别 2:限制工具和输出
const LEVEL_2 = {
  maxTurns: 10,
  model: 'claude-sonnet-4-6',
  allowedTools: ['Read', 'Grep', 'Glob'], // 只读工具
};

// 级别 3:完整能力
const LEVEL_3 = {
  maxTurns: 30,
  model: 'claude-sonnet-4-6',
  allowedTools: ['Read', 'Grep', 'Glob', 'Bash', 'Edit'],
};

function selectLevel(fileCount: number, budgetUsd: number) {
  // 按文件数和预算自动选择级别
  if (fileCount > 20 || budgetUsd < 0.5) return LEVEL_1;
  if (fileCount > 5 || budgetUsd < 2.0) return LEVEL_2;
  return LEVEL_3;
}

5.3 安全沙箱模式

在 CI 环境中,--dangerously-skip-permissions 听起来可怕,但配合以下策略是安全的:

# Dockerfile.ci — 最小权限容器
FROM node:20-slim

# 只安装必要工具
RUN apt-get update && apt-get install -y --no-install-recommends \
    git && rm -rf /var/lib/apt/lists/*

# 非 root 用户
RUN useradd -m -s /bin/bash claude
USER claude
WORKDIR /home/claude/app

# 只读挂载代码(Docker run 时加 --read-only)
COPY --chown=claude:claude . .

# 环境变量通过 CI secrets 注入
ENV ANTHROPIC_API_KEY=""
# 运行:只读文件系统 + 网络 API only
docker run --rm \
  --read-only \
  --tmpfs /tmp \
  -e ANTHROPIC_API_KEY="$API_KEY" \
  -v "$(pwd)":/app:ro \
  claude-review:latest \
  claude -p "审查代码" \
    --max-turns 10 \
    --dangerously-skip-permissions \
    --output-format json

第六部分:调试与排错指南

6.1 调试模式

# 开启详细日志
DEBUG=claude-code:* claude -p "测试" --output-format stream-json

# 查看 SDK 子进程通信
TRACE=1 tsx your-agent-script.ts

6.2 常见错误速查表

错误信息 原因 解决方案
ECONNREFUSED CLI 未安装或认证过期 运行 claude 交互模式重新认证
Max turns exceeded 达到轮次上限 增大 --max-turns 或简化任务
Tool not allowed 工具不在白名单 检查 --allowedTools 配置
Hook timeout Hook 脚本执行超时 优化脚本性能,避免阻塞操作
JSON parse error 输出格式不匹配 确认 --output-format json 已设置
Rate limit API 调用频率过高 添加请求间隔,控制并发数
Session not found resume 的会话不存在 不使用 --resume 或确认会话 ID

6.3 性能优化清单

✅ 并发控制:3-5 个 Agent 并行,避免过多导致 API 限流
✅ 模型选择:审查用 Haiku,复杂重构用 Sonnet
✅ 工具最小化:只开放任务必需的工具
✅ 输入裁剪:diff 限制 5000 字符,文件过大只发片段
✅ 缓存复用:相同输入缓存 SDK 结果,避免重复调用
✅ 超时保护:所有 Agent 调用设置 2-5 分钟超时
✅ 增量执行:只审查变更文件,不全量扫描

延伸阅读

训练营适用性评估

维度 评估
适用训练营类型 AI 工具链训练营、DevOps 自动化训练营、团队工程效能训练营
学员基础要求 TypeScript 基础 + Shell 脚本 + Claude Code 交互模式使用经验
预计教学时长 4-6 小时(含实操)
实操占比 70%(5 个渐进式练习:Print 模式 → 流式监听 → Hook 配置 → 多 Agent 并行 → 完整项目)
硬件要求 每人需要 Claude Code API 额度(建议 $10/人)
难度等级 ⭐⭐⭐⭐(中高级,适合有 Claude Code 基础的开发者)
最佳配套课程 Round 2(Skills 开发)+ Round 3(MCP 服务器)可形成完整工具链

参考来源

  1. Run Claude Code programmatically — Anthropic 官方文档 — Headless 模式和 Agent SDK 的权威参考
  2. Hooks reference — Claude Code Docs — Hook 事件类型、JSON Schema 和配置格式
  3. Claude Code in CI/CD: headless patterns for real pipelines — Refactix — 生产级 CI/CD 集成模式和成本控制策略
  4. Claude Code Batch Processing Complete Guide — SmartScope — /batch 命令和 Headless 模式的批量处理实践
  5. CI & Automation — Claude Code Best Practices — Shell 脚本集成、权限策略和 Docker 沙箱配置

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入