ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

claude-code-base-action 源码解读:Claude Agent SDK 如何驱动整个 Action

claude-code-base-action 源码解读:Claude Agent SDK 如何驱动整个 Action

【免费下载链接】claude-code-base-action

This repo is a mirror of the contents of base-action in https://github.com/anthropics/claude-code-action.

项目地址:https://gitcode.com/gh_mirrors/cl/claude-code-base-action
点击查看免费下载

如果你想在 GitHub Actions 里跑 Claude Code,claude-code-base-action是最值得精读的一份源码:它用不到千行 TypeScript,把 Claude Agent SDK 从"库"变成了整个 CI 流水线里真正的"发动机"。本文带你完整走一遍它的执行链路——从action.yml的输入声明,到 src/index.ts 的编排入口,再到 src/run-claude-sdk.ts 中那个逐条消费 SDK 消息流的query()调用,看懂 Claude Agent SDK 是如何驱动整个 Action 运转的。

🧭 项目全景:三个文件看懂架构

这个项目是一个"薄封装"(thin wrapper):它自己不做信任边界,只负责安装环境 → 校验配置 → 组装参数 → 调用 SDK 执行 → 输出结果。理解它只需要抓住三层:

层次文件职责
声明层action.yml定义所有 inputs/outputs,并用 composite steps 装好 Node、Bun、Claude Code 可执行文件
编排层src/index.ts把 7 个模块按顺序串成流水线,最终设置 Action outputs
引擎层src/run-claude-sdk.ts调用 Claude Agent SDK 的query(),消费消息流并判定成败

依赖极少,核心就一个:package.json 中的@anthropic-ai/claude-agent-sdk。这正是"SDK 驱动整个 Action"的题眼——没有spawnCLI 去解析终端输出,而是通过 SDK 的异步迭代器拿到结构化的消息流。

🔁 执行流程:从 Action 输入到 SDK 调用

先看 action.yml 的runs.steps,它是一个 composite action,依次做四件事:

  1. Setup Node.js(可缓存 npm 依赖)
  2. Install Bun(运行时,版本锁定 1.3.14)
  3. Install Dependencies:bun install --production
  4. Install Claude Code:安装官方 CLI 到~/.local/bin/claude,内置 3 次重试与 120 秒超时

最后一步bun run ${GITHUB_ACTION_PATH}/src/index.ts才真正进入 TypeScript 世界。src/index.ts 的run()按固定顺序执行 6 个环节:

  1. setupWorkloadIdentity()—— 若配置了身份联邦,先换取 OIDC token(src/workload-identity.ts)
  2. validateEnvironmentVariables()—— 按所选 Provider(直连 API / Bedrock / Vertex / Foundry)校验凭证,报错信息非常清晰,见 src/validate-env.ts
  3. setupClaudeCodeSettings()—— 把用户传入的settings(JSON 字符串或文件路径)合并进~/.claude/settings.json,见 src/setup-claude-code-settings.ts
  4. installPlugins()—— 逐个校验并安装插件市场与插件,见 src/install-plugins.ts
  5. preparePrompt()—— 统一提示词来源,见 src/prepare-prompt.ts
  6. runClaude()—— 核心执行,解析参数后交给 SDK

注意第 1 步的注释逻辑:workload identity 必须在环境变量校验之前完成,因为 CLI 需要拿到身份 token 文件后才能认证。失败路径同样周到:catch里先补写execution_file输出再setFailed,finally里清理 token 材料,避免凭证残留。

🚀 核心引擎:run-claude-sdk.ts 如何消费 SDK 消息流

这是全文最关键的文件。看 src/run-claude.ts:它只是个 7 行的转发器——先parseSdkOptions(options),再调用runClaudeWithSdk()。真正的逻辑在 src/run-claude-sdk.ts 的runClaudeWithSdk()中,精华是这一段:

for await (const message of query({ prompt, options: sdkOptions })) { messages.push(message); // sanitizeSdkOutput 决定日志里打印什么 if (message.type === "result") break; }

Claude Agent SDK 的query()是一个异步生成器,它会以SDKMessage序列"直播" Claude Code 会话的全过程:system.init(初始化)、assistant(回复与工具调用)、result(最终结果)。这个 Action 对消息流做了四件关键的事:

  • 安全脱敏:sanitizeSdkOutput()(L117-L160)默认只打印初始化和结果摘要,避免日志泄露密钥;仅当show_full_output: true时才输出完整 JSON
  • 收集全量消息:所有消息写入 execution-file.ts 生成的claude-execution-output.json(位于RUNNER_TEMP),成为execution_file输出,供后续 step 解析
  • 提取 session_id:从system.init消息中取出,暴露为session_id输出,可用--resume续聊
  • 严格判定成败:subtype === "success" && !is_error才算成功;若超出maxTurns、或声明了--json-schema却没拿到structured_output,都会直接抛错让 CI 标红(L241-L278)

还有个耐人寻味的细节:拿到result后显式break(L198-L210)。注释解释了原因——某些 PR 触发的场景中 SDK 迭代器不会自行关闭,for-await会一直挂到 workflow 超时,导致"任务实际成功却被标为 cancelled"。这是典型的"读源码才知道的坑"。

⚙️ 参数工程:parse-sdk-options.ts 的隐藏功力

parse-sdk-options.ts 负责把用户写在claude_args里的 shell 字符串翻译成 SDK 的Options对象,细节很多:

  • shell 元字符转义:(、)、|、&等字符对shell-quote是控制符,直接解析会把--allowedTools Bash(gh:*)悄悄截成Bash,等于放大权限。源码用 Unicode 私用区字符做临时转义再还原(L35-L55)
  • 累积型 flag:allowed-tools、mcp-config等支持多值,用\x00分隔累积
  • 多 MCP 配置合并:内联 JSON 的mcpServers会被 merge 成一份,见mergeMcpConfigs()(L67-L110)
  • 环境变量瘦身:主动删掉ACTIONS_ID_TOKEN_REQUEST_URL等变量,防止 Claude 会话自己"签发" OIDC token(L286-L294)
  • 系统提示词策略:默认使用 SDK 内置的claude_codepreset;用户可用--system-prompt全量替换,或用--append-system-prompt追加(L296-L312)

🔐 免密钥认证:workload-identity.ts 的优雅设计

不想在 secrets 里存 API key?这个模块给出了完整答案:它获取 GitHub OIDC token(JWT),写入RUNNER_TEMP下的文件并导出ANTHROPIC_IDENTITY_TOKEN_FILE,再由 Claude Code CLI 用联邦规则换取短生命周期访问令牌。两个工程亮点值得新手学习:

  • 后台刷新:GitHub OIDC token 有效期短,所以每 4 分钟重写一次 token 文件,stop()时清理(L178-L194)
  • 凭据缓存目录指纹:配置目录名内嵌联邦输入的 SHA-256 指纹,防止同一 job 里不同 step 误复用上一条链路的 token(L80-L109)

配套的指数退避重试封装在 src/retry.ts,默认 3 次、5 秒起步、20 秒封顶。

📤 四个 Outputs 如何被下游消费

action.yml 声明了 4 个输出,全部来自 src/index.ts 的core.setOutput:

输出用途
conclusionsuccess/failure,决定后续 step 是否执行
execution_file执行日志 JSON 路径,可用jq或脚本提取 Claude 的最终答复
structured_output传了--json-schema时的结构化结果,适合机器读取
session_id用于--resume恢复会话

典型玩法见 README 的 PR 评审示例:先判断conclusion == 'success',再从execution_file里反向查找type === 'result'的消息拿到评审内容发评论。examples/issue-triage.yml 则展示了"issue 打开 → Claude 打标签"的完整场景,prompt_file里把任务拆成 5 步,并用allowed_tools精确圈定可用工具。

🧪 本地调试与测试入口

想动手验证?仓库自带完整测试设施:

  • ./test-local.sh:基于act在本地模拟 GitHub Actions 跑整个 Action(需ANTHROPIC_API_KEY)
  • bun test:单元/集成测试,覆盖参数解析、提示词准备、SDK 执行等,如 test/run-claude-sdk.test.ts 用mock.module伪造 SDK 消息流,验证"异常时仍会落盘执行日志"
  • test/mcp-test/:一个最小 MCP server(simple-mcp-server.ts),配合test-mcp-local.sh调试 MCP 集成

架构约定可参考 CLAUDE.md(构建命令、Provider 认证矩阵),开发规范见 CONTRIBUTING.md。

🎯 小结:从这份源码能学到的 3 件事

  1. SDK 优先于 CLI 解析:用 Agent SDK 的异步消息流替代"跑 CLI + 正则解析 stdout",是构建可靠自动化最省心的路线
  2. 安全默认值:日志默认脱敏、环境变量主动瘦身、插件名白名单校验(src/install-plugins.ts)——防御性设计贯穿每个模块
  3. 失败也要留证据:无论 SDK 抛错还是流程中断,执行日志都会尽力落盘并通过execution_file输出,让事后排查有迹可循

读完这一百多行的index.ts加上两百行的run-claude-sdk.ts,你不仅能彻底理解 Claude Agent SDK 如何驱动整个 Action,也拿到了一个可以照抄的"GitHub Action + AI SDK"工程模板。

【免费下载链接】claude-code-base-action

This repo is a mirror of the contents of base-action in https://github.com/anthropics/claude-code-action.

项目地址:https://gitcode.com/gh_mirrors/cl/claude-code-base-action
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进